物理动画参考
Agent 生成物理类 Manim 代码时的模板库。所有代码片段可直接运行。
电场
点电荷电场线
plus = Dot([-2, 0, 0], radius=0.25, color=RED)
plus_label = Text("+", font_size=32, color=WHITE).move_to(plus)
minus = Dot([2, 0, 0], radius=0.25, color=BLUE)
minus_label = Text("-", font_size=32, color=WHITE).move_to(minus)
lines = VGroup()
for angle in np.linspace(0, 2 * PI, 8, endpoint=False):
start = [-2, 0, 0] + 0.35 * np.array([np.cos(angle), np.sin(angle), 0])
pts = [start]
pos = np.array(start, dtype=float)
for _ in range(80):
r1 = pos - np.array([-2.0, 0, 0])
r2 = pos - np.array([2.0, 0, 0])
d1 = np.linalg.norm(r1)
d2 = np.linalg.norm(r2)
if d1 < 0.3 or d2 < 0.3:
break
E = r1 / d1**3 - r2 / d2**3
E = E / np.linalg.norm(E) * 0.15
pos = pos + E
pts.append(pos.copy())
if len(pts) > 2:
line = VMobject(stroke_width=1.5, stroke_color=YELLOW, stroke_opacity=0.6)
line.set_points_smoothly([np.array(p) for p in pts])
lines.add(line)
self.play(FadeIn(plus), FadeIn(plus_label), FadeIn(minus), FadeIn(minus_label))
self.play(LaggedStart(*[Create(l) for l in lines], lag_ratio=0.05), run_time=2)
磁场
条形磁铁磁场线
bar_n = Rectangle(width=0.6, height=2, color=RED, fill_opacity=0.6).shift(UP * 0.5)
bar_s = Rectangle(width=0.6, height=2, color=BLUE, fill_opacity=0.6).shift(DOWN * 0.5)
n_label = Text("N", font_size=24, color=WHITE).move_to(bar_n)
s_label = Text("S", font_size=24, color=WHITE).move_to(bar_s)
magnet = VGroup(bar_s, bar_n, n_label, s_label)
field_lines = VGroup()
for i in range(6):
offset = (i - 2.5) * 0.4
arc = Arc(start_angle=PI * 0.1, angle=PI * 0.8, radius=1.5 + abs(offset) * 0.3,
color=YELLOW, stroke_width=1.5, stroke_opacity=0.5)
arc.shift(UP * 0.5 + RIGHT * offset * 0.3)
field_lines.add(arc)
arc2 = Arc(start_angle=PI * 1.1, angle=PI * 0.8, radius=1.5 + abs(offset) * 0.3,
color=YELLOW, stroke_width=1.5, stroke_opacity=0.5)
arc2.shift(DOWN * 0.5 + RIGHT * offset * 0.3)
field_lines.add(arc2)
self.play(FadeIn(magnet))
self.play(LaggedStart(*[Create(l) for l in field_lines], lag_ratio=0.08), run_time=2)
电磁波
3D 电磁波传播
class EMWaveDemo(ThreeDScene):
def construct(self):
axes = ThreeDAxes(x_range=[0, 8, 1], y_range=[-2, 2, 1], z_range=[-2, 2, 1],
x_length=8, y_length=4, z_length=4)
t = ValueTracker(0)
e_curve = always_redraw(lambda: axes.plot(
lambda x: np.sin(x - t.get_value()),
x_range=[0, 7], color=RED, stroke_width=3,
))
b_curve = always_redraw(lambda: ParametricFunction(
lambda x: axes.c2p(x, 0, np.sin(x - t.get_value())),
t_range=[0, 7], color=BLUE, stroke_width=3,
))
e_label = Text("E", font_size=24, color=RED).move_to([3, 2, 0])
b_label = Text("B", font_size=24, color=BLUE).move_to([3, 0, 2])
self.set_camera_orientation(phi=50 * DEGREES, theta=-60 * DEGREES)
self.play(Create(axes))
self.add(e_curve, b_curve)
self.play(FadeIn(e_label), FadeIn(b_label))
self.play(t.animate.set_value(4 * PI), run_time=4, rate_func=linear)
波动
行波
axes = Axes(x_range=[-1, 10, 1], y_range=[-2, 2, 1], x_length=10, y_length=4,
axis_config={"include_numbers": False, "stroke_width": 1})
t = ValueTracker(0)
def get_wave():
return axes.plot(
lambda x: np.sin(2 * x - t.get_value()),
x_range=[-0.5, 9.5], color="#8b5cf6", stroke_width=3,
)
wave = always_redraw(get_wave)
label = MathTex(r"y = \sin(kx - \omega t)", font_size=30, color="#06b6d4").to_edge(DOWN, buff=0.5)
self.play(Create(axes))
self.add(wave)
self.play(FadeIn(label))
self.play(t.animate.set_value(4 * PI), run_time=4, rate_func=linear)
驻波
axes = Axes(x_range=[0, 7, 1], y_range=[-2, 2, 1], x_length=10, y_length=4,
axis_config={"include_numbers": False, "stroke_width": 1})
t = ValueTracker(0)
def get_standing():
return axes.plot(
lambda x: np.sin(x) * np.cos(t.get_value()),
x_range=[0.05, 6.5], color="#8b5cf6", stroke_width=3,
)
nodes = VGroup(*[Dot(axes.c2p(k * PI, 0), radius=0.08, color=YELLOW) for k in range(1, 3)])
wave = always_redraw(get_standing)
self.play(Create(axes), FadeIn(nodes))
self.add(wave)
self.play(t.animate.set_value(4 * PI), run_time=3, rate_func=linear)
波的干涉
s1 = Dot([-1.5, 0, 0], radius=0.1, color="#8b5cf6")
s2 = Dot([1.5, 0, 0], radius=0.1, color="#8b5cf6")
circles = VGroup()
for i in range(8):
r = 0.5 + i * 0.5
c1 = Circle(radius=r, color="#8b5cf6", stroke_width=1, stroke_opacity=max(0, 0.5 - i * 0.06)).move_to(s1)
c2 = Circle(radius=r, color="#06b6d4", stroke_width=1, stroke_opacity=max(0, 0.5 - i * 0.06)).move_to(s2)
circles.add(c1, c2)
self.play(FadeIn(s1), FadeIn(s2))
self.play(LaggedStart(*[Create(c) for c in circles], lag_ratio=0.08), run_time=2)
力学
抛体运动
ground = Line([-5, -2, 0], [5, -2, 0], color=GREY, stroke_width=2)
v0 = 5
theta = 60 * DEGREES
g = 9.8
t_max = 2 * v0 * np.sin(theta) / g
scale = 0.4
def pos(t):
x = v0 * np.cos(theta) * t * scale - 3
y = (v0 * np.sin(theta) * t - 0.5 * g * t**2) * scale - 2
return [x, y, 0]
ball = Dot(pos(0), radius=0.12, color="#8b5cf6")
trail = TracedPath(lambda: ball.get_center(), stroke_color="#06b6d4", stroke_width=3)
t_tracker = ValueTracker(0)
v_arrow = always_redraw(lambda: Arrow(
ball.get_center(),
ball.get_center() + 0.5 * np.array([
v0 * np.cos(theta) * scale,
(v0 * np.sin(theta) - g * t_tracker.get_value()) * scale, 0
]),
buff=0, color=YELLOW, stroke_width=3,
))
self.play(FadeIn(ground))
self.add(trail, ball, v_arrow)
n_steps = 60
for i in range(1, n_steps + 1):
t = t_tracker.get_value() + t_max / n_steps
self.play(ball.animate.move_to(pos(t)), t_tracker.animate.set_value(t),
run_time=t_max / n_steps, rate_func=linear)
单摆
pivot = Dot([0, 2.5, 0], radius=0.08, color=GREY)
theta = ValueTracker(PI / 4)
length = 2.5
def get_rod():
angle = theta.get_value()
end = [length * np.sin(angle), 2.5 - length * np.cos(angle), 0]
return Line(pivot.get_center(), end, color=GREY, stroke_width=2)
def get_bob():
angle = theta.get_value()
end = [length * np.sin(angle), 2.5 - length * np.cos(angle), 0]
return Dot(end, radius=0.2, color="#8b5cf6")
rod = always_redraw(get_rod)
bob = always_redraw(get_bob)
self.play(FadeIn(pivot))
self.add(rod, bob)
for _ in range(4):
self.play(theta.animate.set_value(-PI / 4), run_time=0.8, rate_func=smooth)
self.play(theta.animate.set_value(PI / 5), run_time=0.8, rate_func=smooth)
self.play(theta.animate.set_value(0), run_time=0.5, rate_func=smooth)
弹簧振子
anchor = Dot([0, 2.5, 0], radius=0.08, color=GREY)
y_val = ValueTracker(0.5)
def get_spring():
y = y_val.get_value()
pts = []
n_coils = 8
for i in range(n_coils * 2 + 1):
t = i / (n_coils * 2)
py = 2.5 - t * (2.5 - y)
px = 0.3 * (1 if i % 2 == 0 else -1) if 0 < i < n_coils * 2 else 0
pts.append([px, py, 0])
spring = VMobject(stroke_width=2.5, color=GREY)
spring.set_points_as_corners([np.array(p) for p in pts])
return spring
spring = always_redraw(get_spring)
mass = always_redraw(lambda: Square(side_length=0.5, color="#8b5cf6", fill_opacity=0.6).move_to([0, y_val.get_value(), 0]))
self.play(FadeIn(anchor))
self.add(spring, mass)
self.play(y_val.animate.set_value(1.5), run_time=0.5, rate_func=smooth)
self.play(y_val.animate.set_value(-0.5), run_time=1, rate_func=smooth)
力的分解(斜面)
plane = Polygon([-3, -2, 0], [3, -2, 0], [3, 1, 0], color=GREY, fill_opacity=0.3, stroke_width=2) block = Square(side_length=0.6, color="#8b5cf6", fill_opacity=0.5, stroke_width=2) block.move_to([1.5, -0.5, 0]).rotate(np.arctan(0.5)) mg = Arrow(block.get_center(), block.get_center() + DOWN * 1.5, buff=0, color=RED, stroke_width=3) mg_label = MathTex(r"mg", font_size=24, color=RED).next_to(mg, RIGHT, buff=0.1) normal = Arrow(block.get_center(), block.get_center() + np.array([-0.5, 1, 0]) * 1.2, buff=0, color=GREEN, stroke_width=3) n_label = MathTex(r"N", font_size=24, color=GREEN).next_to(normal, LEFT, buff=0.1) comp = Arrow(block.get_center(), block.get_center() + np.array([1, 0.5, 0]) * 0.8, buff=0, color="#06b6d4", stroke_width=3) comp_label = MathTex(r"mg\sin\theta", font_size=22, color="#06b6d4").next_to(comp, UR, buff=0.1) self.play(Create(plane)) self.play(FadeIn(block)) self.play(GrowArrow(mg), FadeIn(mg_label)) self.play(GrowArrow(normal), FadeIn(n_label)) self.play(GrowArrow(comp), FadeIn(comp_label))
轨道运动
sun = Dot([0, 0, 0], radius=0.3, color=YELLOW) sun_glow = Dot([0, 0, 0], radius=0.5, color=YELLOW, fill_opacity=0.2) orbit = Circle(radius=2.5, color=GREY, stroke_width=1, stroke_opacity=0.3) planet = Dot([2.5, 0, 0], radius=0.15, color="#06b6d4") trail = TracedPath(lambda: planet.get_center(), stroke_color="#06b6d4", stroke_width=2, stroke_opacity=0.5) self.play(FadeIn(sun_glow), FadeIn(sun), Create(orbit)) self.add(trail, planet) self.play(MoveAlongPath(planet, orbit), run_time=3, rate_func=linear)
热力学
气体分子运动
box = Rectangle(width=6, height=4, color=WHITE, stroke_width=2)
np.random.seed(42)
n_mol = 15
molecules = VGroup()
velocities = []
for _ in range(n_mol):
x, y = np.random.uniform(-2.5, 2.5), np.random.uniform(-1.5, 1.5)
vx, vy = np.random.uniform(-1, 1), np.random.uniform(-1, 1)
d = Dot([x, y, 0], radius=0.1, color="#8b5cf6")
molecules.add(d)
velocities.append(np.array([vx, vy, 0]))
self.play(Create(box), FadeIn(molecules))
for _ in range(60):
new_anims = []
for i, mol in enumerate(molecules):
pos = mol.get_center() + velocities[i] * 0.05
if abs(pos[0]) > 2.8: velocities[i][0] *= -1; pos[0] = np.clip(pos[0], -2.8, 2.8)
if abs(pos[1]) > 1.8: velocities[i][1] *= -1; pos[1] = np.clip(pos[1], -1.8, 1.8)
new_anims.append(mol.animate.move_to(pos))
self.play(*new_anims, run_time=0.05, rate_func=linear)
能量守恒
ke_bar = Rectangle(width=0.8, height=0.1, color="#8b5cf6", fill_opacity=0.7)
pe_bar = Rectangle(width=0.8, height=0.1, color="#06b6d4", fill_opacity=0.7)
ke_bar.move_to([-1.5, -1.5, 0], aligned_edge=DOWN)
pe_bar.move_to([1.5, -1.5, 0], aligned_edge=DOWN)
ke_label = Text("动能", font_size=20, color="#8b5cf6").next_to(ke_bar, DOWN, buff=0.15)
pe_label = Text("势能", font_size=20, color="#06b6d4").next_to(pe_bar, DOWN, buff=0.15)
total_label = MathTex(r"E_{\text{total}} = \text{const}", font_size=28, color=YELLOW).to_edge(UP, buff=0.5)
self.play(FadeIn(ke_label), FadeIn(pe_label), FadeIn(total_label))
self.add(ke_bar, pe_bar)
for ke_h, pe_h in [(2.5, 0.5), (0.5, 2.5), (2.0, 1.0), (1.0, 2.0), (2.5, 0.5)]:
self.play(
ke_bar.animate.stretch_to_fit_height(ke_h).move_to([-1.5, -1.5 + ke_h / 2, 0]),
pe_bar.animate.stretch_to_fit_height(pe_h).move_to([1.5, -1.5 + pe_h / 2, 0]),
run_time=0.8, rate_func=smooth,
)
场景速查表
| 场景 | 核心技术 | 复杂度 |
|---|---|---|
| 电场线 | 数值积分场线 + VMobject | 中 |
| 磁场 | Arc + 循环 | 低 |
| 电磁波 | ThreeDScene + 双曲线 | 高 |
| 行波 | ValueTracker + plot | 低 |
| 驻波 | ValueTracker + 节点 | 低 |
| 干涉 | Circle + LaggedStart | 低 |
| 抛体 | ValueTracker + 轨迹 | 中 |
| 单摆 | ValueTracker + always_redraw | 中 |
| 弹簧 | ValueTracker + zigzag VMobject | 中 |
| 斜面 | Polygon + Arrow 分解 | 低 |
| 轨道 | Circle + MoveAlongPath | 低 |
| 气体 | 随机初始化 + 碰撞循环 | 中 |
| 能量 | stretch_to_fit_height | 低 |
下一步:3b1b 源码 → 动画风格参考