manim 4.0 源码解析
这一切都可以在 https://docs.manim.community/en/stable/guides/deep_dive.html 中找到
首先,我们知道,manim 的主要组成部分为:mobject, animation 和 scene
其中,我们只要理解前两者就可以了,我们不关心 scene 是如何将动画呈现在画布上的
Mobject
Mobject 分为 ImageMobject, PMobject 和 VMobject,顾名思义,分别为 图像物件、点物件和矢量物件,其中最重要的是 VMobject
我们观察 Mobject 的初始化:
def __init__(self, color=WHITE, name=None, dim=3, target=None, z_index=0): self.name = self.__class__.__name__ if name is None else name self.dim = dim self.target = target self.z_index = z_index self.point_hash = None self.submobjects = [] self.updaters = [] self.updating_suspended = False self.color = Color(color) if color else None self.reset_points() self.generate_points() self.init_colors()
可以看到,所有基于 Mobject 的类都会自动 generate_points,这是因为 VMobject 是通过锚点和手柄来呈现的
例如,我们观察几何图形基类 Polygram:
class Polygram(VMobject, metaclass=ConvertToOpenGL): def __init__(self, *vertex_groups: Iterable[Sequence[float]], color=BLUE, **kwargs): super().__init__(color=color, **kwargs) for vertices in vertex_groups: first_vertex, *vertices = vertices first_vertex = np.array(first_vertex) self.start_new_path(first_vertex) self.add_points_as_corners( [*(np.array(vertex) for vertex in vertices), first_vertex], )
它使用了 VMobject 的 start_new_path 和 add_points_as_corners 方法来呈现
Animation
这个动画基类在渲染中这几个方法会被调用:
Animation.begin()
Animation.finish()
Animation.interpolate()
例如,我们观察 Transform 的方法:
def begin(self) -> None: # Use a copy of target_mobject for the align_data # call so that the actual target_mobject stays # preserved. self.target_mobject = self.create_target() self.target_copy = self.target_mobject.copy() # Note, this potentially changes the structure # of both mobject and target_mobject if config.renderer == RendererType.OPENGL: self.mobject.align_data_and_family(self.target_copy) else: self.mobject.align_data(self.target_copy) super().begin() def interpolate_submobject( self, submobject: Mobject, starting_submobject: Mobject, target_copy: Mobject, alpha: float, ) -> Transform: submobject.interpolate(starting_submobject, target_copy, alpha, self.path_func) return self
注意,Animation 的这两个方法几乎等同:
def interpolate(self, alpha: float) -> None: self.interpolate_mobject(alpha)
Scene
它有三个重要的方法:
Scene.setup()
Scene.construct()
Scene.tear_down()
实际上,我们对 Scene 的构造并不感兴趣,因为这部分是最复杂的,它牵涉到实现代码转 mp4 的过程,但是其中含有 Animation 的相关部分。我们注意到,Animation 的 interpolate() 的方法在初始化的时候并没有调用,那么什么时候它起作用了呢?实际上,在 Scene 部分中才真正调用了这个方法:
def update_to_time(self, t): dt = t - self.last_t self.last_t = t for animation in self.animations: animation.update_mobjects(dt) alpha = t / animation.run_time animation.interpolate(alpha) self.update_mobjects(dt) self.update_meshes(dt) self.update_self(dt)
这也就说明了,manim 的动画实现过程部分会杂糅在 Scene 的实现过程中。例如,我们知道,对 VMobject 传入 points 属性,可以实现可视化的贝塞尔曲线,可是我始终没有找到实现这部分原理的方法,可能就是杂糅在 Scene 部分中
Updater
作为另一种实现动画的方法,它和 Animation 的区别在于,Animation 只要确定初始和结束状态,进行插值即可,但 Updater 几乎是对于动画的每一帧进行操作
我并不清楚 Updater 是如何工作的,首先要调用 add_updater 函数:
def add_updater( self, update_function: Updater, index: int | None = None, call_updater: bool = False, ): if index is None: self.updaters.append(update_function) else: self.updaters.insert(index, update_function) if call_updater: update_function(self, 0) return self
函数将 update_function 放入了 mobject 的 updaters 属性中,update 方法对 updaters 属性进行了操作:
def update(self, dt: float = 0, recursive: bool = True): if self.updating_suspended: return self for updater in self.updaters: parameters = get_parameters(updater) if "dt" in parameters: updater(self, dt) else: updater(self) if recursive: for submob in self.submobjects: submob.update(dt, recursive) return self
但是仅仅使用 updater(self) 肯定是不能实现动画效果的,我怀疑这部分也在 Scene 中实现了,并且我也在 Scene 中找到了有关 updater 的部分
我怀疑这部分也是通过 interpolate() 实现的,因为有这样一个 Animation:
class UpdateFromFunc(Animation): def __init__( self, mobject: Mobject, update_function: typing.Callable[[Mobject], typing.Any], suspend_mobject_updating: bool = False, **kwargs, ) -> None: self.update_function = update_function super().__init__( mobject, suspend_mobject_updating=suspend_mobject_updating, **kwargs ) def interpolate_mobject(self, alpha: float) -> None: self.update_function(self.mobject)
但是在 interpolate() 方法中,也仅仅只添加了更新函数,所以这部分待深入研究
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)