2016里约奥运圣火

Table of Contents

奥运圣火

今年的奥运圣火非常有创意, 没有做复杂的点火装置, 全靠后部的圣火盆衬托. 而这个装置的精妙设计以及恰到好处的反光却带来了惊艳的视觉效果:

c7959f5d683a4e34882bc17cedd35849_th.gif

很容易看出它的几何结构, 中间的圆不变, 30 个十字架型以相同的角速度旋转, 但是初相并不一样, 由此带来炫目的螺旋效果.

公式很容易写出(当然我只是目测得到的公式, 不一定对): \[ x = r\times \sin\left(\frac{\theta}{4} + \frac{\pi i}{2} + \omega t\right)\] \[ y = r\times \cos\left(\theta\right) + r\times\cos\left(\frac{\theta}{4} + \frac{\pi i}{2} + \omega t\right)\cos\left(\theta\right)\] \[ z = r\times \sin\left(\theta\right) + r\times\cos\left(\frac{\theta}{4} + \frac{\pi i}{2} + \omega t\right)\sin\left(\theta\right)\]

由于对这个几何体特别有感觉, 我打算用 python 模拟一下.

实现

之前用 matplotlib 这个库也只是做物理实验报告之类的, 并没有写过动画效果, 在看了这位仁兄的Lorenz System之后, 感觉用 matplotlib.animation.FuncAnimation 这个函数还是不难写的, 只需要写一个 animate 每帧更新一下就可以了.

更新时有些细节需要注意, 对于所有 ax.plot, 更新它的坐标需要两步:

line.set_data(x, y)
line.set_3d_properties(z)

这是因为 set_data 只能接收两个参数.

对于所有 ax.scatter, 更新它的坐标只需要一步:

pts._offsets3d = (x, y, z)

因为 offsets 只接收两个参数, 而且没有类似于 set_3d_properties 这样的函数.

做好的效果是这样: <center>![](flame1.gif)</center> 由于没有处理前后关系,所以会有一种缺少层次感的感觉(暂时也没想到什么好办法解决).

导出 gif

可以参考这里.

代码

import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation

plt.clf()
r = 10
fig = plt.figure()
ax = fig.gca(projection='3d')

# ax.view_init(30, 0)
ax.set_xlim3d(-20, 20)
ax.set_ylim3d(-20, 20)
ax.set_zlim3d(-20, 20)
ax.set_aspect('equal')
ax.axis('off')

circle = ax.plot([], [], [], c = 'y', linewidth = 2)
ptss = [ax.scatter([], [], [], c = 'y', s = 10 * (5 - i)) for i in range(5)]
segments = sum([ax.plot([], [], [], c = 'y') for index in range(4 * 30)], [])


def init():
  circle[0].set_data([], [])
  circle[0].set_3d_properties([])

  for segment in segments:
    segment.set_data([], [])
    segment.set_3d_properties([])

  for pts in ptss:
    pts._offsets3d = ([], [], [])

  return circle + segments + ptss

def animate(frame):
  theta = np.linspace(-np.pi, np.pi, 30)
  z = r * np.sin(theta)
  x = [0] * 30
  y = r * np.cos(theta)

  phi = frame * 0.1
  circle[0].set_data(x, y)
  circle[0].set_3d_properties(z)

  seg_cnt = 0
  x_size = y_size = z_size = [[]] * 5
  for i in range(1, 5):
    x_end = x + r * np.sin(theta / 4 + i * np.pi / 2 + phi)
    y_end = y + r * np.cos(theta / 4 + i * np.pi / 2 + phi) * np.cos(theta)
    z_end = z + r * np.cos(theta / 4 + i * np.pi / 2 + phi) * np.sin(theta)
    x_size[0] += x_end.tolist()
    y_size[0] += y_end.tolist()
    z_size[0] += z_end.tolist()

    for j in range(1, 5):
      x_size[j] += (x + .2 * j * r * np.sin(theta / 4 + i * np.pi / 2 + phi)).tolist()
      y_size[j] += (y + .2 * j * r * np.cos(theta / 4 + i * np.pi / 2 + phi) * np.cos(theta)).tolist()
      z_size[j] += (z + .2 * j * r * np.cos(theta / 4 + i * np.pi / 2 + phi) * np.sin(theta)).tolist()

    for a_1, b_1, c_1, a_2, b_2, c_2 in zip(x, y, z, x_end, y_end, z_end):
      segments[seg_cnt].set_data([a_1, a_2], [b_1, b_2])
      segments[seg_cnt].set_3d_properties([c_1, c_2]) 
      seg_cnt += 1

  for i in range(5):
    ptss[i]._offsets3d = (x_size[i], y_size[i], z_size[i])

  ax.view_init(0, frame)
  fig.canvas.draw()
  return circle + segments + ptss

anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=500, interval=30, blit=True)
# anim.save('flame.gif', fps=30, writer = 'imagemagick')
plt.show()  

参考文献

Author: expye(Zihao Ye)

Email: expye@outlook.com

Date: 2016-08-06

Last modified: 2020-07-30 Thu 01:44

Licensed under CC BY-NC 4.0