从一个初中题目说起先来看一道经典的初中动点问题这个题目中当点$ E 和 F 在线段 AD 上移动时点 H 和 G $都随之变换。题目如何解答我们不用管我们的重点是如何实现点$ E 和 F 移动时实时的更新点 H 和 G $。2. 解决方案用Sympy解方程组这里简单说明下Sympy是一个Python的符号计算库可以像数学课本那样进行代数运算。它能解方程、求导积分、化简表达式最重要的是能精确求解方程组返回的是精确的数学解而不是近似值。我们实现这个动画效果时是根据两个直线的方程来求解它的交点的如果没有Sympy我们要手动推导直线方程、联立求解代码会非常冗长且容易出错。从后面的代码你可以看出Sympy让我们只需翻译数学表达式就能得到精确结果大大简化了代码逻辑。我们的思路很简单已知两个点的坐标可以求出直线方程已知两条直线方程联立求解得到交点坐标在Python中用Sympy这个符号计算库来实现再合适不过了。2.1. 第一步定义求直线方程的函数from sympy import Symbol, solve def get_line(p1, p2): 已知两点求直线y kx b的k和b k Symbol(k) b Symbol(b) expr1 p1[0] * k b - p1[1] expr2 p2[0] * k b - p2[1] ret solve((expr1, expr2), dictTrue) return {k: ret[0][k], b: ret[0][b]}2.2. 第二步定义求交点的函数def cross_points(l1, l2): 已知两条直线方程求交点坐标 x Symbol(x) y Symbol(y) expr1 l1[k] * x l1[b] - y expr2 l2[k] * x l2[b] - y ret solve((expr1, expr2), dictTrue) return np.array((float(ret[0][x]), float(ret[0][y]), 0))有了这两个函数我们就可以在Manim中动态更新交点了。2.3. 完整的Manim动画实现from manim import * import numpy as np from sympy import Symbol, solve class DynamicCrossPoint(Scene): def construct(self): # 定义矩形的顶点坐标 points { A: np.array([-2.5, 2, 0]), B: np.array([-2.5, -3, 0]), C: np.array([2.5, -3, 0]), D: np.array([2.5, 2, 0]), } # 初始动点的位置 points[E] np.array([-0.52, 2, 0]) # E在AB上 points[F] np.array([0.52, 2, 0]) # F在CD上且AECF # 画矩形 rectangle Polygon( points[A], points[B], points[C], points[D], stroke_width3, colorGREEN ) self.play(Create(rectangle)) # 创建初始的点和线 d_e Dot(points[E], radius0.05, colorBLUE) d_f Dot(points[F], radius0.05, colorBLUE) d_h Dot(points[A], radius0.05, colorYELLOW) # 初始随便放 l_bf Line(points[B], points[E], colorBLUE, stroke_width2) l_ce Line(points[C], points[F], colorBLUE, stroke_width2) l_bd Line(points[B], points[D], colorGREEN, stroke_width2) self.play(Create(VGroup(l_bf, l_ce, l_bd, d_e, d_f, d_h))) # 核心部分设置更新器 # F点随着E点移动保持AECF的关系 d_f.add_updater( lambda z: z.become( Dot(points[D] - (d_e.get_center() - points[A]), radius0.05, colorBLUE) ) ) # H点是BF和CE的交点 d_h.add_updater( lambda z: z.become( Dot( cross_points( get_line(points[B], d_e.get_center()), # BF get_line(points[C], d_f.get_center()), # CE ), radius0.05, colorYELLOW ) ) ) # 更新线段 l_bf.add_updater( lambda z: z.become( Line(points[B], d_e.get_center(), colorBLUE, stroke_width2) ) ) l_ce.add_updater( lambda z: z.become( Line(points[C], d_f.get_center(), colorBLUE, stroke_width2) ) ) # 让E点动起来 self.play(d_e.animate.shift(LEFT * 1.5), run_time3) self.play(d_e.animate.shift(RIGHT * 3), run_time6) self.play(d_e.animate.shift(LEFT * 1.5), run_time3) # 清理更新器 for mob in [d_f, d_h, l_bf, l_ce]: mob.clear_updaters() self.wait()2.4. 为什么不直接用Manim的几何变换可能有朋友会问Manim不是有.move_to()、.shift()这些方法吗为什么非要用Sympy算交点答案是当动点之间没有简单的几何变换关系时比如在这个例子中交点的位置是由两条动态直线决定的没有办法通过简单的位移或旋转得到。这时数学计算就是最直接的方法。2.5. 进阶思考上面介绍的方法通用性很强可以用来求直线与圆的交点可以用来求两条曲线的交点甚至可以用来求动点轨迹的方程只要你能写出方程Sympy就能帮你解出来。3. 结语今天这个例子我们学会了