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.