77
88import numpy as np
99
10+ from manim ._config import config
11+ from manim .animation .animation import Animation , prepare_animation
12+ from manim .constants import RendererType
13+ from manim .mobject .mobject import Group , Mobject
1014from manim .mobject .opengl .opengl_mobject import OpenGLGroup
15+ from manim .scene .scene import Scene
16+ from manim .utils .iterables import remove_list_redundancies
1117from manim .utils .parameter_parsing import flatten_iterable_parameters
12-
13- from .._config import config
14- from ..animation .animation import Animation , prepare_animation
15- from ..constants import RendererType
16- from ..mobject .mobject import Group , Mobject
17- from ..scene .scene import Scene
18- from ..utils .iterables import remove_list_redundancies
19- from ..utils .rate_functions import linear
18+ from manim .utils .rate_functions import linear
2019
2120if TYPE_CHECKING :
2221 from manim .mobject .opengl .opengl_vectorized_mobject import OpenGLVGroup
23-
24- from ..mobject .types .vectorized_mobject import VGroup
22+ from manim .mobject .types .vectorized_mobject import VGroup
2523
2624__all__ = ["AnimationGroup" , "Succession" , "LaggedStart" , "LaggedStartMap" ]
2725
@@ -93,6 +91,7 @@ def begin(self) -> None:
9391 f"{ self } has a run_time of 0 seconds, this cannot be "
9492 f"rendered correctly. { tmp } ."
9593 )
94+ self .anim_group_time = 0.0
9695 if self .suspend_mobject_updating :
9796 self .group .suspend_updating ()
9897 for anim in self .animations :
@@ -103,8 +102,9 @@ def _setup_scene(self, scene) -> None:
103102 anim ._setup_scene (scene )
104103
105104 def finish (self ) -> None :
106- for anim in self .animations :
107- anim .finish ()
105+ self .interpolate (1 )
106+ self .anims_begun [:] = True
107+ self .anims_finished [:] = True
108108 if self .suspend_mobject_updating :
109109 self .group .resume_updating ()
110110
@@ -116,7 +116,9 @@ def clean_up_from_scene(self, scene: Scene) -> None:
116116 anim .clean_up_from_scene (scene )
117117
118118 def update_mobjects (self , dt : float ) -> None :
119- for anim in self .animations :
119+ for anim in self .anims_with_timings ["anim" ][
120+ self .anims_begun & ~ self .anims_finished
121+ ]:
120122 anim .update_mobjects (dt )
121123
122124 def init_run_time (self , run_time ) -> float :
@@ -133,37 +135,61 @@ def init_run_time(self, run_time) -> float:
133135 The duration of the animation in seconds.
134136 """
135137 self .build_animations_with_timings ()
136- if self .anims_with_timings :
137- self .max_end_time = np .max ([awt [2 ] for awt in self .anims_with_timings ])
138- else :
139- self .max_end_time = 0
138+ # Note: if lag_ratio < 1, then not necessarily the final animation's
139+ # end time will be the max end time! Therefore we must calculate the
140+ # maximum over all the end times, and not just take the last one.
141+ # Example: if you want to play 2 animations of 10s and 1s with a
142+ # lag_ratio of 0.1, the 1st one will end at t=10 and the 2nd one will
143+ # end at t=2, so the AnimationGroup will end at t=10.
144+ self .max_end_time = max (self .anims_with_timings ["end" ], default = 0 )
140145 return self .max_end_time if run_time is None else run_time
141146
142147 def build_animations_with_timings (self ) -> None :
143148 """Creates a list of triplets of the form (anim, start_time, end_time)."""
144- self .anims_with_timings = []
145- curr_time : float = 0
146- for anim in self .animations :
147- start_time : float = curr_time
148- end_time : float = start_time + anim .get_run_time ()
149- self .anims_with_timings .append ((anim , start_time , end_time ))
150- # Start time of next animation is based on the lag_ratio
151- curr_time = (1 - self .lag_ratio ) * start_time + self .lag_ratio * end_time
149+ run_times = np .array ([anim .run_time for anim in self .animations ])
150+ num_animations = run_times .shape [0 ]
151+ dtype = [("anim" , "O" ), ("start" , "f8" ), ("end" , "f8" )]
152+ self .anims_with_timings = np .zeros (num_animations , dtype = dtype )
153+ self .anims_begun = np .zeros (num_animations , dtype = bool )
154+ self .anims_finished = np .zeros (num_animations , dtype = bool )
155+ if num_animations == 0 :
156+ return
157+
158+ lags = run_times [:- 1 ] * self .lag_ratio
159+ self .anims_with_timings ["anim" ] = self .animations
160+ self .anims_with_timings ["start" ][1 :] = np .add .accumulate (lags )
161+ self .anims_with_timings ["end" ] = self .anims_with_timings ["start" ] + run_times
152162
153163 def interpolate (self , alpha : float ) -> None :
154164 # Note, if the run_time of AnimationGroup has been
155165 # set to something other than its default, these
156166 # times might not correspond to actual times,
157167 # e.g. of the surrounding scene. Instead they'd
158168 # be a rescaled version. But that's okay!
159- time = self .rate_func (alpha ) * self .max_end_time
160- for anim , start_time , end_time in self .anims_with_timings :
161- anim_time = end_time - start_time
162- if anim_time == 0 :
163- sub_alpha = 0
164- else :
165- sub_alpha = np .clip ((time - start_time ) / anim_time , 0 , 1 )
166- anim .interpolate (sub_alpha )
169+ anim_group_time = self .rate_func (alpha ) * self .max_end_time
170+ time_goes_back = anim_group_time < self .anim_group_time
171+
172+ # Only update ongoing animations
173+ awt = self .anims_with_timings
174+ new_begun = anim_group_time >= awt ["start" ]
175+ new_finished = anim_group_time > awt ["end" ]
176+ to_update = awt [
177+ (self .anims_begun | new_begun ) & (~ self .anims_finished | ~ new_finished )
178+ ]
179+
180+ run_times = to_update ["end" ] - to_update ["start" ]
181+ sub_alphas = (anim_group_time - to_update ["start" ]) / run_times
182+ if time_goes_back :
183+ sub_alphas [sub_alphas < 0 ] = 0
184+ else :
185+ sub_alphas [sub_alphas > 1 ] = 1
186+
187+ for anim_to_update , sub_alpha in zip (to_update ["anim" ], sub_alphas ):
188+ anim_to_update .interpolate (sub_alpha )
189+
190+ self .anim_group_time = anim_group_time
191+ self .anims_begun = new_begun
192+ self .anims_finished = new_finished
167193
168194
169195class Succession (AnimationGroup ):
@@ -238,8 +264,8 @@ def update_active_animation(self, index: int) -> None:
238264 self .active_animation = self .animations [index ]
239265 self .active_animation ._setup_scene (self .scene )
240266 self .active_animation .begin ()
241- self .active_start_time = self .anims_with_timings [index ][1 ]
242- self .active_end_time = self .anims_with_timings [index ][2 ]
267+ self .active_start_time = self .anims_with_timings [index ]["start" ]
268+ self .active_end_time = self .anims_with_timings [index ]["end" ]
243269
244270 def next_animation (self ) -> None :
245271 """Proceeds to the next animation.
@@ -256,7 +282,7 @@ def interpolate(self, alpha: float) -> None:
256282 self .next_animation ()
257283 if self .active_animation is not None and self .active_start_time is not None :
258284 elapsed = current_time - self .active_start_time
259- active_run_time = self .active_animation .get_run_time ()
285+ active_run_time = self .active_animation .run_time
260286 subalpha = elapsed / active_run_time if active_run_time != 0.0 else 1.0
261287 self .active_animation .interpolate (subalpha )
262288
0 commit comments