物理动画参考
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 源码 → 动画风格参考