The Mobject class has an add_updater method, which allows you to specify a function that will be executed whenever the object changes the video frame.
manilib.mobject.mobject.Mobject.add_updater(self, update_function, index=None, call_updater=True)
The add_updater function adds 'update_function' to 'updaters', a list of functions in charge of each frame of an object.
If the index number is specified, insert 'update_function' at the index number location. The functions in 'updaters' are called according to the order added when the animation is in progress.
Parameters: update_function
update function to be executed
Parameters: index=None
Index number specifying where to go in the object's updaters list
Parameters: call_updater=True
If true, update(0) is called. In general, use the default value of True.
Update using the changing property values of other objects
Let's create an animation that moves the object along the x-axis as shown in the figure below, and displays the object's x-axis value above the object.
Coding strategy is,
- Create an x-axis and make animations that a dot move back and forth on the x-axis.
(x-axis is by NumberLine, dot is by Dot, animation is by rate_func=there_and_back) - Create a DecimalNumber for expressing numbers, and create a function to change the numeric position and numeric content according to the position of the point and specify it as add_updater method.
x_axis = NumberLine(x_min=-5, x_max=5)
dot = Dot(color=RED, radius=0.15).move_to(x_axis.get_left())
number = DecimalNumber(-5, color=RED).next_to(dot, UP)
number.add_updater(lambda m: m.next_to(dot, UP))
number.add_updater(lambda m: m.set_value(dot.get_center()[0]))
self.add(x_axis,dot,number)
self.play(dot.shift, RIGHT * 10, rate_func=there_and_back, run_time=10)
self.wait()
Let's focus on the animated part of the code.
As I created the point, I placed it on the leftmost side of the x-axis, -5, and used the shift method to move the dot to x=5 position with animation for 10 seconds.
At this time, if rate_func= there_and_back, it takes animation to go back and forth from the starting point to the ending point.
self.play(dot.shift, RIGHT * 10, rate_func=there_and_back, run_time=10)
Create two functions according to the position of the moving point and add each using add_updater method.
1) The function that moves the position of the DecimalNumber according to the movement of the point
2) Function to make the value of a number change according to the x-axis value of a point
Functions can be created separately, but they are simply made in one line using lambda expression.
number.add_updater(lambda m: m.next_to(dot, UP))
number.add_updater(lambda m: m.set_value(dot.get_center()[0]))
IIn the above code, since the position value of [x,y,z] is returned by dot.get_center(), coding with dot.get_center()[0] to take the x-axis value.
The point in the example above is to make the dot animated, and to change the position and value of a numeric object as the point moves.
If you think about this by splitting it into video frame units, a 15fps video is 15 frames per second, so the time interval between frames is dt = 1/15 = 0.067 seconds.
Therefore, the update function of a DecimalNumber object will be called every 0.67 seconds, and at this time, the position and value of the DecimalNumber will be changed by reading the dot position.
In the picture above, the time interval for each frame is 1/15=0.067 seconds, and the position of the dot is read at each time interval.
By the way, the speed of time is not constant because the rate_func=there_and_back in the self.play, and accordingly, the position value interval of the dot read in each frame is not constant.
To make this interval constant, change to rate_func=linear.
Udpates using variable values of ValueTracker
Let's take another example.
The previous example was based on the position of another object. This time, the value of the object is changed based on the value of ValueTracker.
ValueTracker is an object whose value changes from the starting value to the target value while the animation is running.
For example, if the execution time is t= 10 seconds, and the starting value s=0 and the target value e=10, the value increases from 0.00 to 10.00 for 10 seconds. (0.00 is only expressed as 2 decimal places, and the actual value can be up to the number of decimal places allowed by the float variable in python)
And, the interval between values ranging from 0 to 10 seconds from 0.00 to 10.00 depends on the rate_func used for self-play. In the case of rate_func=linear, since the speed is constant, the interval of changing the value is the same (0.00 for 0 seconds, 1.00 for 1 second .... 10.00 for 10 seconds), but other functions are not constant.
The example below is an animation of increasing numbers like a digital clock.
vt = ValueTracker(0)
num = DecimalNumber(0, num_decimal_places=3)
num.add_updater(lambda m: m.set_value(vt.get_value()))
self.add(num)
self.play(vt.set_value, 10, rate_func=linear, run_time=10)
self.wait()
Notice the rate_func=linear in play in the code above.
If rate_func=smooth, the number increases slowly when it approaches 10, the end number.
If rate_func=there_and_back, the number is already increased to 10 in 5 seconds, and then the number is decreased from 10 to 0 again.
In other words, the value of ValueTracker is a position value over time.
The value of ValueTracker is set to run_time=10 so that the value starts at 0 and changes to 10. When there is a point that moves from 0 to 10 on the x-axis, the position value of this point is the value of ValueTracker.
So, with rate_func=there_and_back, the position of the point goes from x=0 to x=10 and then back from 10 to 0, so the value of ValueTracker also goes from 0 to 10 and then decreases again to 0.
Use 'dt', the frame change time
The key to using the update function depends on what external values are referenced in this update function.
In other words, if the position of any other object changes as the animation progresses (= as the frame changes), you can refer to the value.
Alternatively, you can create an object called ValueTracker and animate it to use the changed value of ValueTracker.
These are the two methods I have seen above.
In addition to using the fluctuation value of the external object as above, there is a method of using the time value of the frame changing. If the video resolution has 15 frames per second, the time between frames is 1/15 second, which is dt. dt = 1/15 = 0.0668 seconds
The value of this dt can be calculated with the camera.frame_rate in the Scene class.
calculated_dt = 1 / self.camera.frame_rate #여기서 self는 Scene
The update function has a function that automatically calculates and passes this dt value. Just write dt as a parameter when creating the update function.
Let's look at an example using this.
If you want to animate the ellipse as shown below, how do you make the Dot rotate around the circle in one second? As we have seen above, it is not easy to make with other object's movement or ValueTracker. A simple way is to use dt.
Since the value of dt is 1/15 (assuming that fps=15), if you create an offset value to which this dt value is added in update_func, this offset value will be incremented by 1/15, and it will be 1 if added 15 times.
Therefore, we can use this offset value to move the distance corresponding to the offset ratio of the total length of the ellipse.
(To find the position of the ratio with respect to the total length of the object, use the point_from_proportion method)
def orbit_moving(self):
orbit = Ellipse(color=WHITE).scale(3)
dot = Dot(radius=0.3, color=GREEN)
dot.move_to(orbit.point_from_proportion(0))
self.t_offset = 0
rate = 1
def update_func(mob, dt):
self.t_offset += (dt * rate)
mob.move_to(orbit.point_from_proportion(self.t_offset % 1))
dot.add_updater(update_func)
self.add(orbit, dot)
self.wait(1)
When we run this code, it starts at the starting point, turns, and stops just before the starting point.
This is because the t_offset value in the last 15 frames is 14/15 instead of 1. This is because the value of dt in frame 1 is 0.
The dt value is set as 1/fps value only from the second frame.
(I expect this is because the dt value has to be calculated as the frame progresses)
Therefore, when fine-grained control is required, it should be carefully coded.
If we want to return the dot to where it left off in 1 second, we should change code to make a table as below.
For coding according to this table, simply set the initial t_offset value to 1/15. The self.camera.frame_rate value is used for this.
self.t_offset = 1/self.camera.frame_rate
To make one turn in 2 seconds, rate=0.5. This is because t_offset is incremented by 1/2, so 30 frames must be t_offset=1, so that it will rotate one trun.
rate = 1
def update_func(mob, dt):
self.t_offset += (dt * rate)
The code below is a code that rotates one wheel in 2 seconds, rests for 1 second, and then rotates again.
In the code, the remove_updater method removes the function added by add_updater.
def orbit_moving_fullcircle(self):
orbit = Ellipse(color=WHITE).scale(3)
dot = Dot(radius=0.3, color=GREEN)
dot.move_to(orbit.point_from_proportion(0))
self.t_offset = 1/self.camera.frame_rate
rate = 0.5
self.t_idx = 1
def update_func(mob, dt):
self.t_offset += (dt * rate)
mob.move_to(orbit.point_from_proportion(self.t_offset % 1))
print(str(self.t_idx) + ":", str(dt), str(self.t_offset))
self.t_idx += 1
dot.add_updater(update_func)
self.add(orbit, dot)
self.wait(2)
dot.remove_updater(update_func)
self.wait(1)
Next: [06-3-B] Use always_redraw
Go To: [99] Table of Contents
'Programming > Manim Lectures' 카테고리의 다른 글
[06-3-C] Use Updating Class (0) | 2020.06.09 |
---|---|
[06-3-B] Use always_redraw (0) | 2020.06.09 |
[06-3] Animation by Object Updating (0) | 2020.06.09 |
[06-2] Animation by Object's Method (0) | 2020.06.09 |
[06-1-I] Animation : Special Effect (0) | 2020.06.09 |