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() 方法中,也仅仅只添加了更新函数,所以这部分待深入研究

posted @   树叶本子  阅读(220)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示