From 5750a40bc6a47fa5742f0fb2718ab7c04d6daa25 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 22 Mar 2015 16:15:29 -0600 Subject: [PATCH] Preliminaries of scene-oriented model implemented --- animate.py | 211 ++++++++++++--------------------------------------- constants.py | 2 +- displayer.py | 13 ++-- scene.py | 80 +++++++++++++++++++ 4 files changed, 135 insertions(+), 171 deletions(-) create mode 100644 scene.py diff --git a/animate.py b/animate.py index 619de43d..e49bf24f 100644 --- a/animate.py +++ b/animate.py @@ -17,10 +17,8 @@ import displayer as disp class Animation(object): def __init__(self, mobject, + run_time = DEFAULT_ANIMATION_RUN_TIME, alpha_func = high_inflection_0_to_1, - run_time = DEFAULT_ANIMATION_RUN_TIME, - pause_time = DEFAULT_ANIMATION_PAUSE_TIME, - dither_time = DEFAULT_DITHER_TIME, name = None): if isinstance(mobject, type) and issubclass(mobject, Mobject): self.mobject = mobject() @@ -34,24 +32,14 @@ class Animation(object): self.reference_mobjects = [self.starting_mobject] self.alpha_func = alpha_func or (lambda x : x) self.run_time = run_time - self.pause_time = pause_time - self.dither_time = dither_time - self.nframes, self.ndither_frames = self.get_frame_count() - self.nframes_past = 0 - self.frames = [] - self.concurrent_animations = [] - self.following_animations = [] - self.reference_animations = [] - self.background_mobjects = [] self.filter_functions = [] self.restricted_height = SPACE_HEIGHT self.restricted_width = SPACE_WIDTH self.spacial_center = np.zeros(3) - self.name = self.__class__.__name__ + str(self.mobject) - self.inputted_name = name + self.name = name or self.__class__.__name__ + str(self.mobject) def __str__(self): - return self.inputted_name or self.name + return self.name def get_points_and_rgbs(self): """ @@ -59,24 +47,10 @@ class Animation(object): the space. Returns np array of points and corresponding np array of rgbs """ - points = np.zeros(0) - rgbs = np.zeros(0) - for mobject in self.background_mobjects + [self.mobject]: - points = np.append(points, mobject.points) - rgbs = np.append(rgbs, mobject.rgbs) - #Kind of hacky - if mobject.SHOULD_BUFF_POINTS: #TODO, think about this. - up_nudge = np.array( - (2.0 * SPACE_HEIGHT / HEIGHT, 0, 0) - ) - side_nudge = np.array( - (0, 2.0 * SPACE_WIDTH / WIDTH, 0) - ) - for nudge in up_nudge, side_nudge, up_nudge + side_nudge: - points = np.append(points, mobject.points + nudge) - rgbs = np.append(rgbs, mobject.rgbs) - points = points.reshape((points.size/3, 3)) - rgbs = rgbs.reshape((rgbs.size/3, 3)) + #TODO, I don't think this should be necessary. This should happen + #under the individual mobjects. + points = self.mobject.points + rgbs = self.mobject.rgbs #Filters out what is out of bounds. admissibles = (abs(points[:,0]) < self.restricted_width) * \ (abs(points[:,1]) < self.restricted_height) @@ -93,91 +67,27 @@ class Animation(object): admissibles = admissibles[:rgbs.shape[0]] return points[admissibles, :], rgbs[admissibles, :] - def update(self): - if self.nframes_past > self.nframes: - return False - self.nframes_past += 1 - for anim in self.concurrent_animations + self.reference_animations: - anim.update() - self.update_mobject(self.alpha_func(self.get_fraction_complete())) - return True + def update(self, alpha): + if alpha < 0: + alpha = 0 + if alpha > 1: + alpha = 1 + self.update_mobject(self.alpha_func(alpha)) - def while_also(self, action, display = True, *args, **kwargs): - if isinstance(action, type) and issubclass(action, Animation): - self.reference_animations += [ - action(mobject, *args, **kwargs) - for mobject in self.reference_mobjects + [self.mobject] - ] - self.name += action.__name__ - return self - if action.mobject == self.mobject: - #This is just for a weird edge case - action.mobject = self.starting_mobject - new_home = self.concurrent_animations if display else \ - self.reference_animations - new_home.append(action) - self.name += str(action) - return self + # def generate_frames(self): + # print "Generating " + str(self) + "..." + # progress_bar = progressbar.ProgressBar(maxval=self.nframes) + # progress_bar.start() - def with_background(self, *mobjects): - for anim in [self] + self.following_animations: - anim.background_mobjects.append(CompoundMobject(*mobjects)) - return self - - def then(self, action, carry_over_background = False, *args, **kwargs): - if isinstance(action, type) and issubclass(action, Animation): - action = action(mobject = self.mobject, *args, **kwargs) - if carry_over_background: - action.background_mobjects += self.background_mobjects - self.following_animations.append(action) - if self.frames: - self.frames += action.get_frames() - self.name += "Then" + str(action) - return self - - def get_image(self): - all_points, all_rgbs = self.get_points_and_rgbs() - for anim in self.concurrent_animations: - new_points, new_rgbs = anim.get_points_and_rgbs() - all_points = np.append(all_points, new_points) - all_rgbs = np.append(all_rgbs, new_rgbs) - all_points = all_points.reshape((all_points.size/3, 3)) - all_rgbs = all_rgbs.reshape((all_rgbs.size/3, 3)) - return disp.get_image(all_points, all_rgbs) - - def generate_frames(self): - print "Generating " + str(self) + "..." - progress_bar = progressbar.ProgressBar(maxval=self.nframes) - progress_bar.start() - - self.frames = [] - while self.update(): - self.frames.append(self.get_image()) - progress_bar.update(self.nframes_past - 1) - self.clean_up() - for anim in self.following_animations: - self.frames += anim.get_frames() - progress_bar.finish() - return self - - def get_fraction_complete(self): - result = float(self.nframes_past - self.ndither_frames) / ( - self.nframes - 2 * self.ndither_frames) - if result <= 0: - return 0 - elif result >= 1: - return 1 - return result - - def get_frames(self): - if not self.frames: - self.generate_frames() - return self.frames - - def get_frame_count(self): - nframes = int((self.run_time + 2*self.dither_time)/ self.pause_time) - ndither_frames = int(self.dither_time / self.pause_time) - return nframes, ndither_frames + # self.frames = [] + # while self.update(): + # self.frames.append(self.get_image()) + # progress_bar.update(self.nframes_past - 1) + # self.clean_up() + # for anim in self.following_animations: + # self.frames += anim.get_frames() + # progress_bar.finish() + # return self def filter_out(self, *filter_functions): self.filter_functions += filter_functions @@ -193,22 +103,10 @@ class Animation(object): def shift(self, vector): self.spacial_center += vector - for anim in self.following_animations: - anim.shift(vector) return self - def set_dither(self, time, apply_to_concurrent = False): - self.dither_time = time - if apply_to_concurrent: - for anim in self.concurrent_animations + self.reference_animations: - anim.set_dither(time) - return self.reload() - - def set_run_time(self, time, apply_to_concurrent = False): + def set_run_time(self, time): self.run_time = time - if apply_to_concurrent: - for anim in self.concurrent_animations + self.reference_animations: - anim.set_run_time(time) return self.reload() def set_alpha_func(self, alpha_func): @@ -218,30 +116,17 @@ class Animation(object): return self def set_name(self, name): - self.inputted_name = name + self.name = name return self - def reload(self): - self.nframes, self.ndither_frames = self.get_frame_count() - if self.frames: - self.nframes_past = 0 - self.generate_frames() - return self + # def drag_pixels(self): + # self.frames = drag_pixels(self.get_frames()) + # return self - def drag_pixels(self): - self.frames = drag_pixels(self.get_frames()) - return self - - def reverse(self): - self.get_frames().reverse() - self.name = 'Reversed' + str(self) - return self - - def write_to_gif(self, name = None): - disp.write_to_gif(self, name or str(self)) - - def write_to_movie(self, name = None): - disp.write_to_movie(self, name or str(self)) + # def reverse(self): + # self.get_frames().reverse() + # self.name = 'Reversed' + str(self) + # return self def update_mobject(self, alpha): #Typically ipmlemented by subclass @@ -249,9 +134,6 @@ class Animation(object): def clean_up(self): pass - - def dither(self): - pass ###### Concrete Animations ######## @@ -263,12 +145,11 @@ class Rotating(Animation): axes = [[0, 0, 1], [0, 1, 0]], radians = 2 * np.pi, run_time = 20.0, - dither_time = 0.0, alpha_func = None, *args, **kwargs): Animation.__init__( self, mobject, - run_time = run_time, dither_time = dither_time, + run_time = run_time, alpha_func = alpha_func, *args, **kwargs ) @@ -282,19 +163,20 @@ class Rotating(Animation): self.radians * alpha, axis ) + class RotationAsTransform(Rotating): - def __init__(self, mobject, radians, + def __init__(self, mobject, radians, axis = (0, 0, 1), axes = None, run_time = DEFAULT_ANIMATION_RUN_TIME, - dither_time = DEFAULT_DITHER_TIME, + alpha_func = high_inflection_0_to_1, *args, **kwargs): Rotating.__init__( self, mobject, - axis = (0, 0, 1), + axis = axis, + axes = axes, run_time = run_time, - dither_time = dither_time, radians = radians, - alpha_func = high_inflection_0_to_1, + alpha_func = alpha_func, ) class FadeOut(Animation): @@ -309,7 +191,8 @@ class Reveal(Animation): #TODO, Why do you need to do this? Shouldn't points always align? class Transform(Animation): - def __init__(self, mobject1, mobject2, run_time = DEFAULT_TRANSFORM_RUN_TIME, + def __init__(self, mobject1, mobject2, + run_time = DEFAULT_TRANSFORM_RUN_TIME, *args, **kwargs): count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() Mobject.align_data(mobject1, mobject2) @@ -353,7 +236,7 @@ class Transform(Animation): class ApplyMethod(Transform): def __init__(self, method, mobject, *args, **kwargs): """ - method is a method of Mobject + Method is a method of Mobject """ method_args = () if isinstance(method, tuple): @@ -434,6 +317,7 @@ class ComplexHomotopy(Homotopy): class ShowCreation(Animation): def update_mobject(self, alpha): + #TODO, shoudl I make this more efficient? new_num_points = int(alpha * self.starting_mobject.points.shape[0]) for attr in ["points", "rgbs"]: setattr( @@ -444,10 +328,9 @@ class ShowCreation(Animation): class Flash(Animation): def __init__(self, mobject, color = "white", slow_factor = 0.01, - run_time = 0.1, dither_time = 0, alpha_func = None, + run_time = 0.1, alpha_func = None, *args, **kwargs): Animation.__init__(self, mobject, run_time = run_time, - dither_time = dither_time, alpha_func = alpha_func, *args, **kwargs) self.intermediate = Mobject(color = color) diff --git a/constants.py b/constants.py index 6dd5e947..d1f5ee56 100644 --- a/constants.py +++ b/constants.py @@ -8,7 +8,7 @@ DEFAULT_POINT_DENSITY_1D = 200 if PRODUCTION_QUALITY else 50 HEIGHT = 1024#1440 if PRODUCTION_QUALITY else 480 WIDTH = 1024#2560 if PRODUCTION_QUALITY else 640 #All in seconds -DEFAULT_ANIMATION_PAUSE_TIME = 0.04 if PRODUCTION_QUALITY else 0.1 +DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1 DEFAULT_ANIMATION_RUN_TIME = 3.0 DEFAULT_TRANSFORM_RUN_TIME = 1.0 DEFAULT_DITHER_TIME = 1.0 diff --git a/displayer.py b/displayer.py index a8181daa..42f6fb69 100644 --- a/displayer.py +++ b/displayer.py @@ -36,20 +36,21 @@ def get_pixels(points, rgbs): pixels[indices] = rgbs return pixels.reshape((HEIGHT, WIDTH, 3)).astype('uint8') -def write_to_gif(animation, name): +def write_to_gif(scene, name): #TODO, find better means of compression if not name.endswith(".gif"): name += ".gif" filepath = os.path.join(GIF_DIR, name) temppath = os.path.join(GIF_DIR, "Temp.gif") print "Writing " + name + "..." - writeGif(temppath, animation.get_frames(), animation.pause_time) + writeGif(temppath, scene.frames, scene.frame_duration) print "Compressing..." os.system("gifsicle -O " + temppath + " > " + filepath) os.system("rm " + temppath) -def write_to_movie(animation, name): - frames = animation.get_frames() +def write_to_movie(scene, name): + #TODO, incorporate pause time + frames = scene.frames progress_bar = progressbar.ProgressBar(maxval=len(frames)) progress_bar.start() print "writing " + name + "..." @@ -75,7 +76,7 @@ def write_to_movie(animation, name): "-c:v", "libx264", "-vf", - "fps=%d,format=yuv420p"%int(1/animation.pause_time), + "fps=%d,format=yuv420p"%int(1/scene.frame_duration), filepath ] os.system(" ".join(commands)) @@ -87,7 +88,7 @@ def write_to_movie(animation, name): # filepath = os.path.join(MOVIE_DIR, name + ".mov") # fourcc = cv2.cv.FOURCC(*"8bps") # out = cv2.VideoWriter( - # filepath, fourcc, 1.0/animation.pause_time, (WIDTH, HEIGHT), True + # filepath, fourcc, 1.0/animation.frame_duration, (WIDTH, HEIGHT), True # ) # progress = 0 # for frame in frames: diff --git a/scene.py b/scene.py new file mode 100644 index 00000000..9ca345d9 --- /dev/null +++ b/scene.py @@ -0,0 +1,80 @@ +from PIL import Image +from colour import Color +import numpy as np +import warnings +import time +import os +import copy +import progressbar +import inspect + + +from helpers import * +from mobject import * +from animate import * +import displayer as disp + +class Scene(object): + def __init__(self, + frame_duration = DEFAULT_FRAME_DURATION, + name = None): + self.frame_duration = frame_duration + self.frames = [] + self.mobjects = set([]) + self.name = name + + def __str__(self): + return self.name or "Babadinook" #TODO + + def add(self, *mobjects): + #TODO, perhaps mobjects should be ordered, for foreground/background + self.mobjects.update(mobjects) + + def remove(self, *mobjects): + self.mobjects.difference_update(mobjects) + + def animate(self, animations, + dither_time = DEFAULT_DITHER_TIME): + if isinstance(animations, Animation): + animations = [animations] + self.pause(dither_time) + run_time = max([anim.run_time for anim in animations]) + + print "Generating animations..." + progress_bar = progressbar.ProgressBar(maxval=run_time) + progress_bar.start() + + for t in np.arange(0, run_time, self.frame_duration): + progress_bar.update(t) + for anim in animations: + anim.update(t) + self.frames.append(self.get_frame(*animations)) + for anim in animations: + anim.clean_up() + progress_bar.finish() + + def pause(self, duration): + self.frames += [self.get_frame()]*int(duration / self.frame_duration) + + def get_frame(self, *animations): + #Include animations so as to display mobjects not in the list + #TODO, This is temporary + mob = list(self.mobjects)[0] + return disp.get_image(mob.points, mob.rgbs) + + def write_to_gif(self, name = None, end_dither_time = DEFAULT_DITHER_TIME): + self.pause(end_dither_time) + disp.write_to_gif(self, name or str(self)) + + def write_to_movie(self, name = None, end_dither_time = DEFAULT_DITHER_TIME): + self.pause(end_dither_time) + disp.write_to_movie(self, name or str(self)) + + + + + + + + +