使用Manim演示小球直线运动轨迹成圆

今天我在互联网找到一个极为令我震惊的 Manim 演示效果,如果不是眼见为实的看到了实际效果,断然不敢相信这是真的,毕竟初中数学课本上没有讲过这个数学知识,同样是九年义务教育,别人怎么这么优秀。

1.引入库文件

Manim 代码的实现,是通过 Python 的代码库,所以代码开始的时候,需要引入相关的函数库:

1
2
from manim import * 
import numpy as np

2.引入相关场景

1
2
class RadialDotsAnimation(Scene):
def construct(self):

3.定义小球数量

1
2
3
4
# 小球数量
num_dots = 48
text_num_dots = Text(f"Number of Dots: {num_dots}").scale(0.5).to_corner(UL)
self.play(Write(text_num_dots))

4.定义小球半径和颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
# 最大半径
max_radius = 2
# 小球颜色列表
colors = [
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY
]

5.创建小球和径向直线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 创建小球和它们的径向直线
dots = []
lines = []

for i in range(num_dots):
# 计算每个小球的角度(均匀分布)
angle = i * PI / num_dots

# 创建径向直线(从中心到边缘)
line = Line(
max_radius * np.array([-2*np.cos(angle), -2*np.sin(angle), 0]),
max_radius * np.array([2*np.cos(angle), 2*np.sin(angle), 0]),
color=GRAY,
stroke_opacity=1
)
lines.append(line)

# 小球初始位置(在直线右端点)
initial_pos = line.point_from_proportion(0.5 + 0.5 * np.sin(TAU - angle))
dot = Dot(point=initial_pos, color=colors[i], radius=0.1)
dots.append(dot)

# 先画出所有径向直线
self.play(
LaggedStart(
*[Create(line) for line in lines],
lag_ratio=0.03,
run_time=1.5
)
)

# 显示小球
self.play(
LaggedStart(
*[GrowFromCenter(dot) for dot in dots],
lag_ratio=0.03,
run_time=1.5
)
)
self.wait(0.5)
# 让小球沿径向直线运动,同时整体形成旋转滚动效果
total_cycles = 2 # 总共完成4个周期
animation_time = 6 # 动画总时间

6.创建小球动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建每个小球的动画
dot_anims = []
for i, (dot, line) in enumerate(zip(dots, lines)):
# 计算每个小球的相位差,实现旋转效果
phase = i * PI / num_dots

def update_dot(dot, alpha, line=line, phase=phase):
# 使用正弦函数计算位置比例,实现往复运动
# 同时添加相位差,使整体产生旋转效果(alpha(0->1))
pos_ratio = 0.5 + 0.5 * np.sin(alpha * total_cycles * TAU - phase)
new_pos = line.point_from_proportion(pos_ratio)
dot.move_to(new_pos)

dot_anims.append(
UpdateFromAlphaFunc(
dot,
update_dot,
run_time=animation_time,
rate_func=linear
)
)

7.播放动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 播放动画 - 使用LaggedStart让小球依次开始运动
# lag_ratio控制每个小球开始运动的延迟时间
self.play(
LaggedStart(
*dot_anims,
lag_ratio= 0, # 调整这个值可以改变延迟时间
run_time=animation_time
)
)

# 结束时的淡出效果
self.play(
LaggedStart(
*[FadeOut(dot) for dot in dots],
*[FadeOut(line) for line in lines],
lag_ratio=0.03,
run_time=1.5
)
)

原谅我简单的分析一些,凑个篇幅,下面来看演示的效果

8.感谢作者的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from manim import * 
import numpy as np

config.background_color = BLACK
config.frame_width = 9
config.frame_height = 12
config.pixel_width = 1080
config.pixel_height = 1440

class RadialDotsAnimation(Scene):
def construct(self):
# 小球数量
num_dots = 48
text_num_dots = Text(f"Number of Dots: {num_dots}").scale(0.5).to_corner(UL)
self.play(Write(text_num_dots))

# 最大半径
max_radius = 2
# 小球颜色列表
colors = [
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY,
RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE,
PINK, TEAL, MAROON, GOLD, DARK_BLUE, LIGHT_GRAY
]

# 创建小球和它们的径向直线
dots = []
lines = []

for i in range(num_dots):
# 计算每个小球的角度(均匀分布)
angle = i * PI / num_dots

# 创建径向直线(从中心到边缘)
line = Line(
max_radius * np.array([-2*np.cos(angle), -2*np.sin(angle), 0]),
max_radius * np.array([2*np.cos(angle), 2*np.sin(angle), 0]),
color=GRAY,
stroke_opacity=1
)
lines.append(line)

# 小球初始位置(在直线右端点)
initial_pos = line.point_from_proportion(0.5 + 0.5 * np.sin(TAU - angle))
dot = Dot(point=initial_pos, color=colors[i], radius=0.1)
dots.append(dot)

# 先画出所有径向直线
self.play(
LaggedStart(
*[Create(line) for line in lines],
lag_ratio=0.03,
run_time=1.5
)
)

# 显示小球
self.play(
LaggedStart(
*[GrowFromCenter(dot) for dot in dots],
lag_ratio=0.03,
run_time=1.5
)
)
self.wait(0.5)

# 让小球沿径向直线运动,同时整体形成旋转滚动效果
total_cycles = 2 # 总共完成4个周期
animation_time = 6 # 动画总时间

# 创建每个小球的动画
dot_anims = []
for i, (dot, line) in enumerate(zip(dots, lines)):
# 计算每个小球的相位差,实现旋转效果
phase = i * PI / num_dots

def update_dot(dot, alpha, line=line, phase=phase):
# 使用正弦函数计算位置比例,实现往复运动
# 同时添加相位差,使整体产生旋转效果(alpha(0->1))
pos_ratio = 0.5 + 0.5 * np.sin(alpha * total_cycles * TAU - phase)
new_pos = line.point_from_proportion(pos_ratio)
dot.move_to(new_pos)

dot_anims.append(
UpdateFromAlphaFunc(
dot,
update_dot,
run_time=animation_time,
rate_func=linear
)
)

# 播放动画 - 使用LaggedStart让小球依次开始运动
# lag_ratio控制每个小球开始运动的延迟时间
self.play(
LaggedStart(
*dot_anims,
lag_ratio= 0, # 调整这个值可以改变延迟时间
run_time=animation_time
)
)

# 结束时的淡出效果
self.play(
LaggedStart(
*[FadeOut(dot) for dot in dots],
*[FadeOut(line) for line in lines],
lag_ratio=0.03,
run_time=1.5
)
)

# 运行动画
if __name__ == "__main__":
scene = RadialDotsAnimation(preview=True)

我个人观点,代码之中对屏幕的设定可以省略,最后面的代码主要是预防报错,我个人不熟悉代码的作用,知道的朋友可以留言告诉我,谢谢大家,谢谢原作者提供的代码,互联网偶得之,如果侵犯了您的权益,请留言告诉我,再次感谢!

每个小球在做直线运动的同时,整体系统因多个小球的运动方向差异,导致观察者看到的是连续的圆周轨迹。这种现象类似于“视觉暂留”效果。