Manim旋转联动动画

1. 概要

最近用Manim制作一个数学动画时,用到了一些旋转的动画。

使用Manim来实现单个物体的旋转,或者绕着固定物体旋转都很简单,但是实现多个物体之间旋转的联动则稍微麻烦一些,这里借用太阳,地球,月亮的自转公转来介绍下多个物体旋转联动的实现思路,以供参考。

2. 地球自转

地球每天自己旋转一天就是自转。在Manim中,自转就是绕着自己的中心点旋转。

from manim import *

class RevolutionRotation(Scene):
    def construct(self):
        self.rotation()
        self.wait()

    # 自转
    def rotation(self):
        # 这是一个表示地球的图片
        earth = ImageMobject("assets\earth")
        self.add(earth)

        self.play(
            Rotate(
                earth,
                about_point=ORIGIN,
                angle=2 * PI,
                axis=IN,
                run_time=2,
                rate_func=linear,
            ),
        )

revolution.gif

3. 地球绕太阳公转

地球绕着太阳转就是公转。和自转的实现类似,公转就是围绕指定的中心点旋转。

from manim import *

class RevolutionRotation(Scene):
    def construct(self):
        self.revolution()
        self.wait()

    # 公转
    def revolution(self):
        earth = ImageMobject("assets\earth")
        sun = ImageMobject("assets\sun")

        earth.scale(0.3).shift(UP * 2)
        self.add(earth, sun)

        self.play(
            Rotate(
                earth,
                about_point=ORIGIN,
                angle=2 * PI,
                axis=IN,
                run_time=2,
                rate_func=linear,
            ),
        )

out.gif

4. 太阳,地球和月亮联动

地球绕着太阳转,月亮绕着地球转。地球绕着太阳旋转和上一步一样,重点在于如何控制月亮的旋转。

我实现的思路是:

  • 计算地球相对于太阳旋转的角度 αalphaα
  • 设置此时月亮相对于地球旋转的角度 βbetaβ
  • 通过Manimupdate机制,当地球旋转αalphaα角度之后
  • 更新月亮相对于地球旋转的角度βbetaβ
  • 下面的示例中,设置了 β=5αbeta = 5alphaβ=5α(设置太大,月亮会旋转太快看不清)
  • 所以地球绕太阳1圈时,月亮绕了地球5圈。
  • from manim import *
    import numpy as np
    
    # 求两个向量的夹角
    def arccos_angle(a, b):
        cos_angle = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
        angle = np.arccos(cos_angle)
        return angle
    
    class RevolutionRotation(Scene):
        def construct(self):
            self.rr()
            self.wait()
            
    	def rr(self):
            earth = ImageMobject("assets\earth")
            sun = ImageMobject("assets\sun")
            moon = ImageMobject("assets\moon")
    
            earth.scale(0.4).shift(UP * 2)
            moon.scale(0.2).shift(UP * 2.8)
            self.add(earth, sun, moon)
    
            # 月亮的旋转角度
            def moon_updater(z):
                # a 是地球初始位置
                a = UP * 2
                # b 是地球当前的位置
                b = earth.get_center()
    
                # ang是地球相对于太阳旋转的角度
                ang = arccos_angle(a, b)
    
                # axis控制月亮左旋还是右旋
                axis = IN
                if b[0] < a[0]:
                    axis = OUT
    
                # 不断更新月亮的位置
                return z.become(
                    ImageMobject("assets\moon")
                    .move_to(b + np.array([0, 0.8, 0]))
                    .scale(0.2)
                    .rotate(
                        angle=ang * 5, # 月亮更新的角度是地球旋转的5倍
                        about_point=b,
                        axis=axis,
                    )
                )
    
            moon.add_updater(moon_updater)
    
            # 地球旋转了1/4暂停一下,为了看效果
            # 此时月亮旋转了1+1/4圈。
            self.play(
                Rotate(
                    earth,
                    about_point=ORIGIN,
                    angle=PI / 2,
                    axis=IN,
                    run_time=2,
                    rate_func=linear,
                ),
            )
            self.wait()
    
            # 地球旋转剩下来的3/4圈
            self.play(
                Rotate(
                    earth,
                    about_point=ORIGIN,
                    angle=3 * PI / 2,
                    axis=IN,
                    run_time=4,
                    rate_func=linear,
                ),
            )
    

    out.gif

    5. 总结

    Manim本身提供的Rotate接口可以实现大部分常见的旋转操作,本篇主要介绍的是多个物体联动的实现思路。

    核心就是每个旋转的物体只要关心它所围绕的物体的位置,根据它所围绕物体的位置来更新自己的位置。比如,上面的示例中,地球的旋转只要关注太阳的位置,月亮的旋转只要关注地球的位置。如果有更多的物体联动旋转,也是一样的思路。