dt updaters#
[1]:
from manim import *
config.media_embed = True; config.media_width = "100%"
_RV = "-v WARNING -qm --progress_bar None --disable_caching Example"
_RI = "-v WARNING -s --progress_bar None --disable_caching Example"
Manim Community v0.15.1
Theory#
All the animations we’ve seen so far absolutely need to be inside the Scene.play parameter, but this isn’t always necessary.
As we said, the updaters are updated every frame, but only when the Scene.play method is executed, let’s see the following example.
[2]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob):
# angle to increment
angle = 3 * DEGREES
# save info
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim $_RV
ANGLE = 0 °
As we can see, nothing happens, and it is what we expected, we are not executing the Scene.play method.
If we want the updaters to always run we can change the Scene.always_update_mobjects attribute to True.
[3]:
class Example(Scene):
def construct(self):
self.always_update_mobjects = True
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob):
# angle to increment
angle = 3 * DEGREES
# save info
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim $_RV
ANGLE = 366.0 °
But this is not always convenient, since it will make the rendering slower since Manim will be aware of the change of all the Mobjects at all times, what we want is that only the selected mobjects are updated.
To make a single Mobject update all the time, add the dt parameter to the updater definition.
Remember to use --disable_caching to avoid problems:
[4]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob, dt): # <- Add dt
angle = 3 * DEGREES
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim $_RV
ANGLE = 366.0 °
But now we have a problem, since now the value of _angle depends on the value of the FPS.
[5]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob, dt): # <- Add dt
angle = 3 * DEGREES
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim -v WARNING -qm --progress_bar None --disable_caching --fps=10 Example
ANGLE = 126.0 °
[6]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob, dt): # <- Add dt
angle = 3 * DEGREES
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim -v WARNING -qm --progress_bar None --disable_caching --fps=20 Example
ANGLE = 246.0 °
To solve this you have to use the same dt parameter, since by definition \({\tt dt} = 1 / {\rm FPS}\).
[7]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob, dt):
angle = 3 * DEGREES * dt
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim -v WARNING -qm --progress_bar None --disable_caching --fps=10 Example
ANGLE = 11.700000000000008 °
[8]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq._angle = 0
def update_sq(mob, dt):
angle = 3 * DEGREES * dt
mob._angle += angle * 180 / PI
mob.rotate(angle)
sq.add_updater(update_sq)
self.add(sq)
self.wait(4)
print("ANGLE = " + str(sq._angle) + " °")
%manim -v WARNING -qm --progress_bar None --disable_caching --fps=30 Example
ANGLE = 11.900000000000015 °
We see that now the final value is more consistent, but not perfect.
The theoretical value of _angle is \(\,3° \times 4\, {\rm secs} = 12 °\).
The error is due to how the Scene class is defined, Manim’s philosophy is that the final project is rendered at 60 FPS, so when you finish your animation (and used dt updaters) it is recommended to render at 60 FPS so you don’t see lag.
The updaters run in the background along with the animations with Scene.play, so we can do things like this:
[9]:
class Example(Scene):
def construct(self):
ellipse = Ellipse().scale(2)
dot = Dot(ellipse.point_from_proportion(0))
dot.pfp = 0
def update_dot(mob, dt):
mob.pfp += dt * (1/4) # 1 cycle every 4 seconds
position = ellipse.point_from_proportion(mob.pfp % 1)
mob.move_to(position)
dot.add_updater(update_dot)
self.add(dot, ellipse)
self.wait(4)
dot.clear_updaters()
self.wait()
%manim $_RV
[10]:
class Example(Scene):
def construct(self):
ellipse = Ellipse().scale(2)
text = Text("dt updaters").scale(2).to_edge(UP)
dot = Dot(ellipse.point_from_proportion(0))
dot.pfp = 0
def update_dot(mob, dt):
mob.pfp += dt * (1/4) # 1 cycle every 4 seconds
position = ellipse.point_from_proportion(mob.pfp % 1)
mob.move_to(position)
dot.add_updater(update_dot)
self.add(dot, ellipse)
self.play(Write(text,run_time=3)) # 3 secs
self.wait() # 1 sec
dot.clear_updaters()
self.wait()
%manim $_RV
Simulations#
The dt updaters are good for simulations:
[11]:
class Example(Scene):
def construct(self):
THETA_MAX = 10 * DEGREES
L = 3
g = 15
W = np.sqrt(g / L)
T = 2 * PI / W
# Line definition
line = Line(LEFT*3,RIGHT*3)
line.rotate(-PI/2)
mass = Dot(color=RED)\
.scale(4).move_to(line.get_end())
line.add(mass)
line.save_state()
line._angle = T/4 # initial angle
pivot = line.get_start()
# roof
roof = VGroup(*[
Line(ORIGIN,UR).scale(0.4).copy()
for _ in range(8)
]).arrange(RIGHT,buff=0.1)
roof.add(Line(roof.get_corner(DL),roof.get_corner(DR)))
roof.next_to(line.get_top(),UP,buff=0)
def line_update(mob, dt):
mob.restore()
angle = THETA_MAX * np.sin(W * mob._angle)
mob._angle += dt
mob.rotate(angle, about_point=pivot)
line.add_updater(line_update)
self.add(line,roof)
self.wait(10)
line.clear_updaters()
self.wait()
%manim $_RV
Class animations to dt updaters#
The last functionality of the updaters is to transform class animations to dt updaters, it is something really simple if you have already understood the previously explained theory.
[12]:
class Example(Scene):
def construct(self):
text = Text("Animation")
anim = Write(text,run_time=3)
turn_animation_into_updater(anim)
self.add(text)
self.wait(4)
text.clear_updaters()
self.wait()
%manim $_RV
We can even add extra updaters
[13]:
class Example(Scene):
def construct(self):
text = Text("Animation",color=WHITE).scale(3)
text._color = WHITE
text._alpha = 0
anim = Write(text,run_time=3)
turn_animation_into_updater(anim)
def update_text(mob, dt):
alpha = there_and_back(mob._alpha % 1)
mob.set_color(
interpolate_color(mob._color, RED, alpha)
)
mob._alpha += dt * 0.7
text.add_updater(update_text)
self.add(text)
self.wait(7)
text.clear_updaters()
self.wait()
%manim $_RV
We can avoid having to define the update_text function by using anonymous functions:
[14]:
class Example(Scene):
def construct(self):
text = Text("Animation",color=WHITE).scale(3)
text._color = WHITE
text._alpha = 0
anim = Write(text,run_time=3)
turn_animation_into_updater(anim)
text.add_updater(
lambda mob, dt: mob.set_color(interpolate_color(
mob._color, RED, there_and_back(mob.set(_alpha=dt * 0.7 + mob._alpha)._alpha % 1)
))
)
self.add(text)
self.wait(7)
text.clear_updaters()
self.wait()
%manim $_RV
Unfortunately we cannot merge animations like in the previous chapter.