Added Mobject.update capabilties, which should be nicer and lighter weight than using ContinualUpdateFromFunc all the time. Also changed the behavior of any Animation having its mobject automatically thrust up to the front of the screen

This commit is contained in:
Grant Sanderson 2018-08-12 12:17:32 -07:00
parent a72ce98f5e
commit 5b5a83b316
5 changed files with 97 additions and 28 deletions

View file

@ -123,6 +123,4 @@ class Animation(object):
if surrounding_scene is not None: if surrounding_scene is not None:
if self.is_remover(): if self.is_remover():
surrounding_scene.remove(self.mobject) surrounding_scene.remove(self.mobject)
else:
surrounding_scene.add(self.mobject)
return self return self

View file

@ -1,6 +1,4 @@
import copy import copy
import itertools as it import itertools as it
import numpy as np import numpy as np
@ -22,6 +20,7 @@ from utils.paths import straight_path
from utils.space_ops import angle_of_vector from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3 from utils.space_ops import complex_to_R3
from utils.space_ops import rotation_matrix from utils.space_ops import rotation_matrix
from utils.simple_functions import get_num_args
from functools import reduce from functools import reduce
@ -46,6 +45,7 @@ class Mobject(Container):
self.color = Color(self.color) self.color = Color(self.color)
if self.name is None: if self.name is None:
self.name = self.__class__.__name__ self.name = self.__class__.__name__
self.updaters = []
self.init_points() self.init_points()
self.generate_points() self.generate_points()
self.init_colors() self.init_colors()
@ -98,6 +98,8 @@ class Mobject(Container):
setattr(self, attr, func(getattr(self, attr))) setattr(self, attr, func(getattr(self, attr)))
return self return self
# Displaying
def get_image(self, camera=None): def get_image(self, camera=None):
if camera is None: if camera is None:
from camera.camera import Camera from camera.camera import Camera
@ -142,6 +144,42 @@ class Mobject(Container):
self.target = self.copy() self.target = self.copy()
return self.target return self.target
# Updating
def update(self, dt):
for updater in self.updaters:
num_args = get_num_args(updater)
if num_args == 1:
updater(self)
elif num_args == 2:
updater(self, dt)
else:
raise Exception(
"Mobject updater expected 1 or 2 "
"arguments, %d given" % num_args
)
def get_time_based_updaters(self):
return [
udpater
for updater in self.updaters
if get_num_args(updater) == 2
]
def get_updaters(self):
return self.updaters
def add_updater(self, update_function):
self.updaters.append(update_function)
def remove_updater(self, update_function):
while update_function in self.updaters:
self.updaters.remove(update_function)
return self
def clear_updaters(self):
self.updaters = []
# Transforming operations # Transforming operations
def apply_to_family(self, func): def apply_to_family(self, func):
@ -604,6 +642,7 @@ class Mobject(Container):
def get_color(self): def get_color(self):
return self.color return self.color
## ##
def save_state(self, use_deepcopy=False): def save_state(self, use_deepcopy=False):

View file

@ -13,7 +13,7 @@ from utils.iterables import stretch_array_to_length
class PMobject(Mobject): class PMobject(Mobject):
CONFIG = { CONFIG = {
"stroke_width": DEFAULT_POINT_THICKNESS, "stroke_width": DEFAULT_STROKE_WIDTH,
} }
def init_points(self): def init_points(self):

View file

@ -172,9 +172,9 @@ class Scene(Container):
self.clear() self.clear()
### ###
def continual_update(self, dt=None): def continual_update(self, dt):
if dt is None: for mobject in self.get_mobjects():
dt = self.frame_duration mobject.update(dt)
for continual_animation in self.continual_animations: for continual_animation in self.continual_animations:
continual_animation.update(dt) continual_animation.update(dt)
@ -188,7 +188,17 @@ class Scene(Container):
self.continual_animations = [ca for ca in self.continual_animations if ca in continual_animations] self.continual_animations = [ca for ca in self.continual_animations if ca in continual_animations]
def should_continually_update(self): def should_continually_update(self):
return len(self.continual_animations) > 0 or self.always_continually_update if self.always_continually_update:
return True
if len(self.continual_animations) > 0:
return True
any_time_based_update = any([
len(m.get_time_based_updaters()) > 0
for m in self.get_mobjects()
])
if any_time_based_update:
return True
return False
### ###
@ -230,6 +240,7 @@ class Scene(Container):
mobjects, continual_animations = self.separate_mobjects_and_continual_animations( mobjects, continual_animations = self.separate_mobjects_and_continual_animations(
mobjects_or_continual_animations mobjects_or_continual_animations
) )
mobjects += self.foreground_mobjects
self.restructure_mobjects(to_remove=mobjects) self.restructure_mobjects(to_remove=mobjects)
self.mobjects += mobjects self.mobjects += mobjects
self.continual_animations += continual_animations self.continual_animations += continual_animations
@ -330,15 +341,24 @@ class Scene(Container):
return [m.copy() for m in self.mobjects] return [m.copy() for m in self.mobjects]
def get_moving_mobjects(self, *animations): def get_moving_mobjects(self, *animations):
moving_mobjects = list(it.chain( # Go through mobjects from start to end, and
[ # as soon as there's one that needs updating of
anim.mobject for anim in animations # some kind per frame, return the list from that
if anim.mobject not in self.foreground_mobjects # point forward.
], animation_mobjects = [anim.mobject for anim in animations]
[ca.mobject for ca in self.continual_animations], ca_mobjects = [ca.mobject for ca in self.continual_animations]
self.foreground_mobjects, mobjects = self.get_mobjects()
)) for i, mob in enumerate(mobjects):
return moving_mobjects update_possibilities = [
mob in animation_mobjects,
mob in ca_mobjects,
len(mob.get_updaters()) > 0,
mob in self.foreground_mobjects
]
for possibility in update_possibilities:
if possibility:
return mobjects[i:]
return []
def get_time_progression(self, run_time): def get_time_progression(self, run_time):
if self.skip_animations: if self.skip_animations:
@ -439,25 +459,30 @@ class Scene(Container):
# This is where kwargs to play like run_time and rate_func # This is where kwargs to play like run_time and rate_func
# get applied to all animations # get applied to all animations
animation.update_config(**kwargs) animation.update_config(**kwargs)
# Anything animated that's not already in the
# scene gets added to the scene
if animation.mobject not in self.mobjects:
self.add(animation.mobject)
moving_mobjects = self.get_moving_mobjects(*animations) moving_mobjects = self.get_moving_mobjects(*animations)
# Paint all non-moving objects onto the screen, so they don't # Paint all non-moving objects onto the screen, so they don't
# have to be rendered every frame # have to be rendered every frame
self.update_frame(excluded_mobjects=moving_mobjects) self.update_frame(excluded_mobjects=moving_mobjects)
static_image = self.get_frame() static_image = self.get_frame()
total_run_time = 0
for t in self.get_animation_time_progression(animations): for t in self.get_animation_time_progression(animations):
self.continual_update(dt=t - total_run_time)
for animation in animations: for animation in animations:
animation.update(t / animation.run_time) animation.update(t / animation.run_time)
self.continual_update()
self.update_frame(moving_mobjects, static_image) self.update_frame(moving_mobjects, static_image)
self.add_frames(self.get_frame()) self.add_frames(self.get_frame())
self.add(*moving_mobjects) total_run_time = t
self.mobjects_from_last_animation = moving_mobjects self.mobjects_from_last_animation = [
anim.mobject for anim in animations
]
self.clean_up_animations(*animations) self.clean_up_animations(*animations)
if self.skip_animations: if self.skip_animations:
# Todo, not great that this uses a variable from self.continual_update(total_run_time)
# a previous loop...
self.continual_update(t)
else: else:
self.continual_update(0) self.continual_update(0)
self.num_plays += 1 self.num_plays += 1
@ -466,7 +491,6 @@ class Scene(Container):
def clean_up_animations(self, *animations): def clean_up_animations(self, *animations):
for animation in animations: for animation in animations:
animation.clean_up(self) animation.clean_up(self)
self.add(*self.foreground_mobjects)
return self return self
def get_mobjects_from_last_animation(self): def get_mobjects_from_last_animation(self):
@ -476,17 +500,20 @@ class Scene(Container):
def wait(self, duration=DEFAULT_WAIT_TIME): def wait(self, duration=DEFAULT_WAIT_TIME):
if self.should_continually_update(): if self.should_continually_update():
total_time = 0
for t in self.get_time_progression(duration): for t in self.get_time_progression(duration):
self.continual_update() self.continual_update(dt=t - total_time)
self.update_frame() self.update_frame()
self.add_frames(self.get_frame()) self.add_frames(self.get_frame())
total_time = t
elif self.skip_animations: elif self.skip_animations:
# Do nothing # Do nothing
return self return self
else: else:
self.update_frame() self.update_frame()
self.add_frames(*[self.get_frame()] * n_frames = int(duration / self.frame_duration)
int(duration / self.frame_duration)) frame = self.get_frame()
self.add_frames(*[frame] * n_frames)
return self return self
def wait_to(self, time, assert_positive=True): def wait_to(self, time, assert_positive=True):

View file

@ -1,5 +1,6 @@
import numpy as np import numpy as np
import operator as op import operator as op
import inspect
from functools import reduce from functools import reduce
@ -16,6 +17,10 @@ def choose(n, r):
numer = reduce(op.mul, range(n, n - r, -1), 1) numer = reduce(op.mul, range(n, n - r, -1), 1)
return numer // denom return numer // denom
def get_num_args(function):
return len(inspect.signature(function).parameters)
# Just to have a less heavyweight name for this extremely common operation # Just to have a less heavyweight name for this extremely common operation
# #
# We may wish to have more fine-grained control over division by zero behavior # We may wish to have more fine-grained control over division by zero behavior