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,
|
||||
# especially any mobject copying, should live in
|
||||
# this method
|
||||
self.mobject.set_animating_status(True)
|
||||
self.starting_mobject = self.create_starting_mobject()
|
||||
if self.suspend_mobject_updating:
|
||||
# All calls to self.mobject's internal updaters
|
||||
|
@ -66,6 +67,7 @@ class Animation(object):
|
|||
|
||||
def finish(self) -> None:
|
||||
self.interpolate(self.final_alpha_value)
|
||||
self.mobject.set_animating_status(False)
|
||||
if self.suspend_mobject_updating:
|
||||
self.mobject.resume_updating()
|
||||
|
||||
|
|
|
@ -199,7 +199,10 @@ class Camera(object):
|
|||
self.init_textures()
|
||||
self.init_light_source()
|
||||
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:
|
||||
self.frame = CameraFrame(**self.frame_config)
|
||||
|
@ -365,11 +368,21 @@ class Camera(object):
|
|||
if render_group["single_use"]:
|
||||
self.release_render_group(render_group)
|
||||
|
||||
def get_render_group_list(self, mobject: Mobject) -> list[dict[str]] | map[dict[str]]:
|
||||
try:
|
||||
return self.static_mobject_to_render_group_list[id(mobject)]
|
||||
except KeyError:
|
||||
return map(self.get_render_group, mobject.get_shader_wrapper_list())
|
||||
def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str]]:
|
||||
if mobject.is_changing():
|
||||
return self.generate_render_group_list(mobject)
|
||||
|
||||
# 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(
|
||||
self,
|
||||
|
@ -408,19 +421,10 @@ class Camera(object):
|
|||
if render_group[key] is not None:
|
||||
render_group[key].release()
|
||||
|
||||
def set_mobjects_as_static(self, *mobjects: Mobject) -> None:
|
||||
# Creates buffer and array objects holding each mobjects shader data
|
||||
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.static_mobject_to_render_group_list = {}
|
||||
def refresh_static_mobjects(self) -> None:
|
||||
for render_group in it.chain(*self.mob_to_render_groups.values()):
|
||||
self.release_render_group(render_group)
|
||||
self.mob_to_render_groups = {}
|
||||
|
||||
# Shaders
|
||||
def init_shaders(self) -> None:
|
||||
|
|
|
@ -71,7 +71,7 @@ class Mobject(object):
|
|||
# Must match in attributes of vert shader
|
||||
"shader_dtype": [
|
||||
('point', np.float32, (3,)),
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -81,6 +81,8 @@ class Mobject(object):
|
|||
self.family: list[Mobject] = [self]
|
||||
self.locked_data_keys: set[str] = set()
|
||||
self.needs_new_bounding_box: bool = True
|
||||
self._is_animating: bool = False
|
||||
self._is_movable: bool = False
|
||||
|
||||
self.init_data()
|
||||
self.init_uniforms()
|
||||
|
@ -421,7 +423,6 @@ class Mobject(object):
|
|||
self.center()
|
||||
return self
|
||||
|
||||
|
||||
def sort(
|
||||
self,
|
||||
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())
|
||||
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
|
||||
|
||||
def shift(self, vector: np.ndarray):
|
||||
|
|
|
@ -115,16 +115,16 @@ class Scene(object):
|
|||
# If there is a window, enter a loop
|
||||
# which updates the frame while under
|
||||
# the hood calling the pyglet event loop
|
||||
log.info("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.")
|
||||
log.info(
|
||||
"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.lock_static_mobject_data()
|
||||
self.refresh_static_mobjects()
|
||||
while not (self.window.is_closing or self.quit_interaction):
|
||||
self.update_frame(1 / self.camera.frame_rate)
|
||||
if self.window.is_closing:
|
||||
self.window.destroy()
|
||||
if self.quit_interaction:
|
||||
self.unlock_mobject_data()
|
||||
|
||||
def embed(self, close_scene_on_exit: bool = True) -> None:
|
||||
if not self.preview:
|
||||
|
@ -141,6 +141,7 @@ class Scene(object):
|
|||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
shell = InteractiveShellEmbed()
|
||||
# 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())
|
||||
# Use the locals of the caller as the local namespace
|
||||
# once embedded, and add a few custom shortcuts
|
||||
|
@ -442,6 +443,7 @@ class Scene(object):
|
|||
self.real_animation_start_time = time.time()
|
||||
self.virtual_animation_start_time = self.time
|
||||
|
||||
self.refresh_static_mobjects()
|
||||
func(self, *args, **kwargs)
|
||||
|
||||
if should_write:
|
||||
|
@ -450,23 +452,8 @@ class Scene(object):
|
|||
self.num_plays += 1
|
||||
return wrapper
|
||||
|
||||
def lock_static_mobject_data(self, *animations: Animation) -> None:
|
||||
movers = list(it.chain(*[
|
||||
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 refresh_static_mobjects(self) -> None:
|
||||
self.camera.refresh_static_mobjects()
|
||||
|
||||
def begin_animations(self, animations: Iterable[Animation]) -> None:
|
||||
for animation in animations:
|
||||
|
@ -506,11 +493,9 @@ class Scene(object):
|
|||
log.warning("Called Scene.play with no animations")
|
||||
return
|
||||
animations = self.anims_from_play_args(*args, **kwargs)
|
||||
self.lock_static_mobject_data(*animations)
|
||||
self.begin_animations(animations)
|
||||
self.progress_through_animations(animations)
|
||||
self.finish_animations(animations)
|
||||
self.unlock_mobject_data()
|
||||
|
||||
@handle_play_like_call
|
||||
def wait(
|
||||
|
@ -523,7 +508,6 @@ class Scene(object):
|
|||
if note:
|
||||
log.info(note)
|
||||
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:
|
||||
while self.hold_on_wait:
|
||||
self.update_frame(dt=1 / self.camera.frame_rate)
|
||||
|
@ -538,7 +522,7 @@ class Scene(object):
|
|||
self.emit_frame()
|
||||
if stop_condition is not None and stop_condition():
|
||||
break
|
||||
self.unlock_mobject_data()
|
||||
self.refresh_static_mobjects()
|
||||
return self
|
||||
|
||||
def wait_until(
|
||||
|
|
Loading…
Add table
Reference in a new issue