mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Change the way changing-vs-static mobjects are tracked
Previously, Camera would keep track of which mobjects are supposed to be "static", so that it could generated their render groups once and not repeat unnecessarily. This had an awkward dependence where Scene would then need to keep track of which mobjects should and should not be considered static. This update pushes that logic to the Mobject level, where it keeps track internally of whether it's being animated, has an updater, or can be moved around by the mouse.
This commit is contained in:
parent
5a91c73b23
commit
50565fcd7a
4 changed files with 55 additions and 47 deletions
|
@ -52,6 +52,7 @@ class Animation(object):
|
||||||
# played. As much initialization as possible,
|
# played. As much initialization as possible,
|
||||||
# especially any mobject copying, should live in
|
# especially any mobject copying, should live in
|
||||||
# this method
|
# this method
|
||||||
|
self.mobject.set_animating_status(True)
|
||||||
self.starting_mobject = self.create_starting_mobject()
|
self.starting_mobject = self.create_starting_mobject()
|
||||||
if self.suspend_mobject_updating:
|
if self.suspend_mobject_updating:
|
||||||
# All calls to self.mobject's internal updaters
|
# All calls to self.mobject's internal updaters
|
||||||
|
@ -66,6 +67,7 @@ class Animation(object):
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
self.interpolate(self.final_alpha_value)
|
self.interpolate(self.final_alpha_value)
|
||||||
|
self.mobject.set_animating_status(False)
|
||||||
if self.suspend_mobject_updating:
|
if self.suspend_mobject_updating:
|
||||||
self.mobject.resume_updating()
|
self.mobject.resume_updating()
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,10 @@ class Camera(object):
|
||||||
self.init_textures()
|
self.init_textures()
|
||||||
self.init_light_source()
|
self.init_light_source()
|
||||||
self.refresh_perspective_uniforms()
|
self.refresh_perspective_uniforms()
|
||||||
self.static_mobject_to_render_group_list = {}
|
# A cached map from mobjects to their associated list of render groups
|
||||||
|
# so that these render groups are not regenerated unnecessarily for static
|
||||||
|
# mobjects
|
||||||
|
self.mob_to_render_groups = {}
|
||||||
|
|
||||||
def init_frame(self) -> None:
|
def init_frame(self) -> None:
|
||||||
self.frame = CameraFrame(**self.frame_config)
|
self.frame = CameraFrame(**self.frame_config)
|
||||||
|
@ -365,11 +368,21 @@ class Camera(object):
|
||||||
if render_group["single_use"]:
|
if render_group["single_use"]:
|
||||||
self.release_render_group(render_group)
|
self.release_render_group(render_group)
|
||||||
|
|
||||||
def get_render_group_list(self, mobject: Mobject) -> list[dict[str]] | map[dict[str]]:
|
def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str]]:
|
||||||
try:
|
if mobject.is_changing():
|
||||||
return self.static_mobject_to_render_group_list[id(mobject)]
|
return self.generate_render_group_list(mobject)
|
||||||
except KeyError:
|
|
||||||
return map(self.get_render_group, mobject.get_shader_wrapper_list())
|
# Otherwise, cache result for later use
|
||||||
|
key = id(mobject)
|
||||||
|
if key not in self.mob_to_render_groups:
|
||||||
|
self.mob_to_render_groups[key] = list(self.generate_render_group_list(mobject))
|
||||||
|
return self.mob_to_render_groups[key]
|
||||||
|
|
||||||
|
def generate_render_group_list(self, mobject: Mobject) -> Iterable[dict[str]]:
|
||||||
|
return (
|
||||||
|
self.get_render_group(sw, single_use=mobject.is_changing())
|
||||||
|
for sw in mobject.get_shader_wrapper_list()
|
||||||
|
)
|
||||||
|
|
||||||
def get_render_group(
|
def get_render_group(
|
||||||
self,
|
self,
|
||||||
|
@ -408,19 +421,10 @@ class Camera(object):
|
||||||
if render_group[key] is not None:
|
if render_group[key] is not None:
|
||||||
render_group[key].release()
|
render_group[key].release()
|
||||||
|
|
||||||
def set_mobjects_as_static(self, *mobjects: Mobject) -> None:
|
def refresh_static_mobjects(self) -> None:
|
||||||
# Creates buffer and array objects holding each mobjects shader data
|
for render_group in it.chain(*self.mob_to_render_groups.values()):
|
||||||
for mob in mobjects:
|
|
||||||
self.static_mobject_to_render_group_list[id(mob)] = [
|
|
||||||
self.get_render_group(sw, single_use=False)
|
|
||||||
for sw in mob.get_shader_wrapper_list()
|
|
||||||
]
|
|
||||||
|
|
||||||
def release_static_mobjects(self) -> None:
|
|
||||||
for rg_list in self.static_mobject_to_render_group_list.values():
|
|
||||||
for render_group in rg_list:
|
|
||||||
self.release_render_group(render_group)
|
self.release_render_group(render_group)
|
||||||
self.static_mobject_to_render_group_list = {}
|
self.mob_to_render_groups = {}
|
||||||
|
|
||||||
# Shaders
|
# Shaders
|
||||||
def init_shaders(self) -> None:
|
def init_shaders(self) -> None:
|
||||||
|
|
|
@ -71,7 +71,7 @@ class Mobject(object):
|
||||||
# Must match in attributes of vert shader
|
# Must match in attributes of vert shader
|
||||||
"shader_dtype": [
|
"shader_dtype": [
|
||||||
('point', np.float32, (3,)),
|
('point', np.float32, (3,)),
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -81,6 +81,8 @@ class Mobject(object):
|
||||||
self.family: list[Mobject] = [self]
|
self.family: list[Mobject] = [self]
|
||||||
self.locked_data_keys: set[str] = set()
|
self.locked_data_keys: set[str] = set()
|
||||||
self.needs_new_bounding_box: bool = True
|
self.needs_new_bounding_box: bool = True
|
||||||
|
self._is_animating: bool = False
|
||||||
|
self._is_movable: bool = False
|
||||||
|
|
||||||
self.init_data()
|
self.init_data()
|
||||||
self.init_uniforms()
|
self.init_uniforms()
|
||||||
|
@ -421,7 +423,6 @@ class Mobject(object):
|
||||||
self.center()
|
self.center()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def sort(
|
def sort(
|
||||||
self,
|
self,
|
||||||
point_to_num_func: Callable[[np.ndarray], float] = lambda p: p[0],
|
point_to_num_func: Callable[[np.ndarray], float] = lambda p: p[0],
|
||||||
|
@ -637,6 +638,23 @@ class Mobject(object):
|
||||||
self.has_updaters = any(mob.get_updaters() for mob in self.get_family())
|
self.has_updaters = any(mob.get_updaters() for mob in self.get_family())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
# Check if mark as static or not for camera
|
||||||
|
|
||||||
|
def is_changing(self) -> bool:
|
||||||
|
return self._is_animating or self.has_updaters or self._is_movable
|
||||||
|
|
||||||
|
def set_animating_status(self, is_animating: bool) -> None:
|
||||||
|
self._is_animating = is_animating
|
||||||
|
|
||||||
|
def set_movable_status(self, is_movable: bool) -> None:
|
||||||
|
self._is_movable = is_movable
|
||||||
|
|
||||||
|
def is_movable(self) -> bool:
|
||||||
|
return self._is_movable
|
||||||
|
|
||||||
|
def make_movable(self) -> None:
|
||||||
|
self._is_movable = True
|
||||||
|
|
||||||
# Transforming operations
|
# Transforming operations
|
||||||
|
|
||||||
def shift(self, vector: np.ndarray):
|
def shift(self, vector: np.ndarray):
|
||||||
|
|
|
@ -115,16 +115,16 @@ class Scene(object):
|
||||||
# If there is a window, enter a loop
|
# If there is a window, enter a loop
|
||||||
# which updates the frame while under
|
# which updates the frame while under
|
||||||
# the hood calling the pyglet event loop
|
# the hood calling the pyglet event loop
|
||||||
log.info("Tips: You are now in the interactive mode. Now you can use the keyboard"
|
log.info(
|
||||||
" and the mouse to interact with the scene. Just press `q` if you want to quit.")
|
"Tips: You are now in the interactive mode. Now you can use the keyboard"
|
||||||
|
" and the mouse to interact with the scene. Just press `q` if you want to quit."
|
||||||
|
)
|
||||||
self.quit_interaction = False
|
self.quit_interaction = False
|
||||||
self.lock_static_mobject_data()
|
self.refresh_static_mobjects()
|
||||||
while not (self.window.is_closing or self.quit_interaction):
|
while not (self.window.is_closing or self.quit_interaction):
|
||||||
self.update_frame(1 / self.camera.frame_rate)
|
self.update_frame(1 / self.camera.frame_rate)
|
||||||
if self.window.is_closing:
|
if self.window.is_closing:
|
||||||
self.window.destroy()
|
self.window.destroy()
|
||||||
if self.quit_interaction:
|
|
||||||
self.unlock_mobject_data()
|
|
||||||
|
|
||||||
def embed(self, close_scene_on_exit: bool = True) -> None:
|
def embed(self, close_scene_on_exit: bool = True) -> None:
|
||||||
if not self.preview:
|
if not self.preview:
|
||||||
|
@ -141,6 +141,7 @@ class Scene(object):
|
||||||
from IPython.terminal.embed import InteractiveShellEmbed
|
from IPython.terminal.embed import InteractiveShellEmbed
|
||||||
shell = InteractiveShellEmbed()
|
shell = InteractiveShellEmbed()
|
||||||
# Have the frame update after each command
|
# Have the frame update after each command
|
||||||
|
shell.events.register('post_run_cell', lambda *a, **kw: self.refresh_static_mobjects())
|
||||||
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame())
|
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame())
|
||||||
# Use the locals of the caller as the local namespace
|
# Use the locals of the caller as the local namespace
|
||||||
# once embedded, and add a few custom shortcuts
|
# once embedded, and add a few custom shortcuts
|
||||||
|
@ -442,6 +443,7 @@ class Scene(object):
|
||||||
self.real_animation_start_time = time.time()
|
self.real_animation_start_time = time.time()
|
||||||
self.virtual_animation_start_time = self.time
|
self.virtual_animation_start_time = self.time
|
||||||
|
|
||||||
|
self.refresh_static_mobjects()
|
||||||
func(self, *args, **kwargs)
|
func(self, *args, **kwargs)
|
||||||
|
|
||||||
if should_write:
|
if should_write:
|
||||||
|
@ -450,23 +452,8 @@ class Scene(object):
|
||||||
self.num_plays += 1
|
self.num_plays += 1
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def lock_static_mobject_data(self, *animations: Animation) -> None:
|
def refresh_static_mobjects(self) -> None:
|
||||||
movers = list(it.chain(*[
|
self.camera.refresh_static_mobjects()
|
||||||
anim.mobject.get_family()
|
|
||||||
for anim in animations
|
|
||||||
]))
|
|
||||||
for mobject in self.mobjects:
|
|
||||||
if mobject in movers or mobject.get_family_updaters():
|
|
||||||
continue
|
|
||||||
self.camera.set_mobjects_as_static(mobject)
|
|
||||||
|
|
||||||
def unlock_mobject_data(self) -> None:
|
|
||||||
self.camera.release_static_mobjects()
|
|
||||||
|
|
||||||
def refresh_locked_data(self):
|
|
||||||
self.unlock_mobject_data()
|
|
||||||
self.lock_static_mobject_data()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def begin_animations(self, animations: Iterable[Animation]) -> None:
|
def begin_animations(self, animations: Iterable[Animation]) -> None:
|
||||||
for animation in animations:
|
for animation in animations:
|
||||||
|
@ -506,11 +493,9 @@ class Scene(object):
|
||||||
log.warning("Called Scene.play with no animations")
|
log.warning("Called Scene.play with no animations")
|
||||||
return
|
return
|
||||||
animations = self.anims_from_play_args(*args, **kwargs)
|
animations = self.anims_from_play_args(*args, **kwargs)
|
||||||
self.lock_static_mobject_data(*animations)
|
|
||||||
self.begin_animations(animations)
|
self.begin_animations(animations)
|
||||||
self.progress_through_animations(animations)
|
self.progress_through_animations(animations)
|
||||||
self.finish_animations(animations)
|
self.finish_animations(animations)
|
||||||
self.unlock_mobject_data()
|
|
||||||
|
|
||||||
@handle_play_like_call
|
@handle_play_like_call
|
||||||
def wait(
|
def wait(
|
||||||
|
@ -523,7 +508,6 @@ class Scene(object):
|
||||||
if note:
|
if note:
|
||||||
log.info(note)
|
log.info(note)
|
||||||
self.update_mobjects(dt=0) # Any problems with this?
|
self.update_mobjects(dt=0) # Any problems with this?
|
||||||
self.lock_static_mobject_data()
|
|
||||||
if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
|
if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
|
||||||
while self.hold_on_wait:
|
while self.hold_on_wait:
|
||||||
self.update_frame(dt=1 / self.camera.frame_rate)
|
self.update_frame(dt=1 / self.camera.frame_rate)
|
||||||
|
@ -538,7 +522,7 @@ class Scene(object):
|
||||||
self.emit_frame()
|
self.emit_frame()
|
||||||
if stop_condition is not None and stop_condition():
|
if stop_condition is not None and stop_condition():
|
||||||
break
|
break
|
||||||
self.unlock_mobject_data()
|
self.refresh_static_mobjects()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def wait_until(
|
def wait_until(
|
||||||
|
|
Loading…
Add table
Reference in a new issue