You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Manim+SciPy/SymPy/Numpy实现最速降线动画并验证小球速度

问题解答

一、现有小球动画的物理合理性与速度计算

1. 物理合理性判断

你当前的动画不符合物理规律:代码中通过ValueTracker的t匀速变化来更新小球的x坐标,相当于小球沿x轴匀速移动,完全忽略了重力作用下的运动规律——真实情况下,小球沿曲线下滑的速度会随下落高度变化,x方向的运动速度并非恒定值。

2. 速度计算方法

假设小球无摩擦下滑,根据机械能守恒,速度大小由下落的高度差决定:

v = √(2gΔh)
其中Δh = y₀ - y(x),y₀是初始位置的y坐标,g是重力加速度(通常取9.8m/s²,简化计算时可设为10)

速度的方向是曲线在当前点的切线方向,可通过对曲线函数求导得到切线斜率,进而推导方向向量。

3. 修正动画的核心思路

要让动画符合物理规律,不能直接让x随时间匀速变化,需通过数值积分求解小球的运动方程:

  • 设曲线为y=f(x),切线斜率k=f’(x),结合速度大小v=√(2g(y₀ - y)),推导x、y方向的速度分量关系
  • 用欧拉法或龙格-库塔法进行数值迭代,逐帧更新小球的x、y位置

二、用Manim制作最速降线(Brachistochrone)动画

最速降线的本质是摆线,参数方程为:

x = r(θ - sinθ)
y = r(1 - cosθ)

其中r为摆线半径,θ取值范围通常为0到2π(对应摆线的一个完整拱)。

完整实现代码

from manim import *
import numpy as np

class Brachistochrone(Scene):
    def construct(self):
        # 设置坐标轴
        ax = Axes(
            x_range=[0, 7], y_range=[0, 4],
            axis_config={"include_tip": False, "color": GREY}
        )
        labels = ax.get_axis_labels(x_label="x", y_label="y")
        self.add(ax, labels)

        # 摆线参数(r=1)
        r = 1
        def brachistochrone(theta):
            return np.array([r*(theta - np.sin(theta)), r*(1 - np.cos(theta)), 0])
        
        # 生成对比曲线:最速降线、直线、抛物线
        theta_space = np.linspace(0, 2*np.pi, 200)
        brachistochrone_points = [brachistochrone(t) for t in theta_space]
        brachistochrone_curve = VMobject().set_points_as_corners(brachistochrone_points).set_color(RED)
        
        # 直线:从起点(0,2r)到终点(2πr,0)
        start_point = ax.c2p(0, 2*r)
        end_point = ax.c2p(2*np.pi*r, 0)
        line = Line(start_point, end_point, color=BLUE)
        
        # 抛物线:y = -0.1(x - πr)^2 + 2r
        def parabola(x):
            return -0.1*(x - np.pi*r)**2 + 2*r
        parabola_curve = ax.plot(parabola, x_range=[0, 2*np.pi*r], color=GREEN)

        # 创建三个对比小球
        dot_b = Dot(start_point, color=RED)
        dot_l = Dot(start_point, color=BLUE)
        dot_p = Dot(start_point, color=GREEN)

        self.add(brachistochrone_curve, line, parabola_curve)
        self.add(dot_b, dot_l, dot_p)

        # 最速降线小球运动更新器
        def update_brachistochrone_dot(dot, dt):
            current_pos = ax.p2c(dot.get_center())
            # 近似匹配当前位置对应的theta
            theta_idx = np.argmin(np.abs(r*(theta_space - np.sin(theta_space)) - current_pos[0]))
            theta = theta_space[theta_idx]
            # 计算速度大小(机械能守恒)
            initial_y = 2*r
            current_y = current_pos[1]
            v = np.sqrt(2*9.8*(initial_y - current_y)) if (initial_y - current_y) > 0 else 0
            # 计算切线方向速度分量
            dx_dtheta = r*(1 - np.cos(theta))
            dy_dtheta = r*np.sin(theta)
            if dx_dtheta == 0:
                return
            speed_norm = np.sqrt(dx_dtheta**2 + dy_dtheta**2)
            v_x = v * dx_dtheta / speed_norm
            v_y = v * dy_dtheta / speed_norm
            # 更新位置(Manim y轴向上,物理下落方向向下,故v_y取负)
            new_x = current_pos[0] + v_x * dt
            new_y = current_pos[1] - v_y * dt
            if new_x >= 2*np.pi*r:
                dot.move_to(end_point)
                return
            dot.move_to(ax.c2p(new_x, new_y))

        # 直线小球运动更新器
        def update_line_dot(dot, dt):
            current_pos = ax.p2c(dot.get_center())
            initial_y = 2*r
            current_y = current_pos[1]
            v = np.sqrt(2*9.8*(initial_y - current_y)) if (initial_y - current_y) > 0 else 0
            # 直线斜率k = -1/π
            k = -1/np.pi
            speed_norm = np.sqrt(1 + k**2)
            v_x = v / speed_norm
            v_y = v * k / speed_norm
            new_x = current_pos[0] + v_x * dt
            new_y = current_pos[1] - v_y * dt
            if new_x >= 2*np.pi*r:
                dot.move_to(end_point)
                return
            dot.move_to(ax.c2p(new_x, new_y))

        # 抛物线小球运动更新器
        def update_parabola_dot(dot, dt):
            current_pos = ax.p2c(dot.get_center())
            initial_y = 2*r
            current_y = current_pos[1]
            v = np.sqrt(2*9.8*(initial_y - current_y)) if (initial_y - current_y) > 0 else 0
            # 抛物线导数y’ = -0.2(x - πr)
            dy_dx = -0.2*(current_pos[0] - np.pi*r)
            speed_norm = np.sqrt(1 + dy_dx**2)
            v_x = v / speed_norm
            v_y = v * dy_dx / speed_norm
            new_x = current_pos[0] + v_x * dt
            new_y = current_pos[1] - v_y * dt
            if new_x >= 2*np.pi*r:
                dot.move_to(end_point)
                return
            dot.move_to(ax.c2p(new_x, new_y))

        # 添加更新器并播放动画
        dot_b.add_updater(update_brachistochrone_dot)
        dot_l.add_updater(update_line_dot)
        dot_p.add_updater(update_parabola_dot)
        self.wait(5)
        # 到达终点后移除更新器
        dot_b.remove_updater(update_brachistochrone_dot)
        dot_l.remove_updater(update_line_dot)
        dot_p.remove_updater(update_parabola_dot)
        self.wait()

关键说明

  • 最速降线采用摆线参数方程生成,这是唯一能让小球从起点到终点耗时最短的曲线
  • 动画通过机械能守恒计算速度,结合曲线切线斜率分解速度分量,用数值积分更新位置,完全符合物理规律
  • 加入直线、抛物线的对比,可直观展示最速降线的“最快”特性

内容的提问来源于stack exchange,提问作者Freya the Goddess

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:24:53