Preliminaries of scene-oriented model implemented

This commit is contained in:
Grant Sanderson 2015-03-22 16:15:29 -06:00
parent 948e1e3038
commit 5750a40bc6
4 changed files with 135 additions and 171 deletions

View file

@ -17,10 +17,8 @@ import displayer as disp
class Animation(object): class Animation(object):
def __init__(self, def __init__(self,
mobject, mobject,
alpha_func = high_inflection_0_to_1,
run_time = DEFAULT_ANIMATION_RUN_TIME, run_time = DEFAULT_ANIMATION_RUN_TIME,
pause_time = DEFAULT_ANIMATION_PAUSE_TIME, alpha_func = high_inflection_0_to_1,
dither_time = DEFAULT_DITHER_TIME,
name = None): name = None):
if isinstance(mobject, type) and issubclass(mobject, Mobject): if isinstance(mobject, type) and issubclass(mobject, Mobject):
self.mobject = mobject() self.mobject = mobject()
@ -34,24 +32,14 @@ class Animation(object):
self.reference_mobjects = [self.starting_mobject] self.reference_mobjects = [self.starting_mobject]
self.alpha_func = alpha_func or (lambda x : x) self.alpha_func = alpha_func or (lambda x : x)
self.run_time = run_time 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.filter_functions = []
self.restricted_height = SPACE_HEIGHT self.restricted_height = SPACE_HEIGHT
self.restricted_width = SPACE_WIDTH self.restricted_width = SPACE_WIDTH
self.spacial_center = np.zeros(3) self.spacial_center = np.zeros(3)
self.name = self.__class__.__name__ + str(self.mobject) self.name = name or self.__class__.__name__ + str(self.mobject)
self.inputted_name = name
def __str__(self): def __str__(self):
return self.inputted_name or self.name return self.name
def get_points_and_rgbs(self): def get_points_and_rgbs(self):
""" """
@ -59,24 +47,10 @@ class Animation(object):
the space. Returns np array of points and corresponding np array the space. Returns np array of points and corresponding np array
of rgbs of rgbs
""" """
points = np.zeros(0) #TODO, I don't think this should be necessary. This should happen
rgbs = np.zeros(0) #under the individual mobjects.
for mobject in self.background_mobjects + [self.mobject]: points = self.mobject.points
points = np.append(points, mobject.points) rgbs = self.mobject.rgbs
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))
#Filters out what is out of bounds. #Filters out what is out of bounds.
admissibles = (abs(points[:,0]) < self.restricted_width) * \ admissibles = (abs(points[:,0]) < self.restricted_width) * \
(abs(points[:,1]) < self.restricted_height) (abs(points[:,1]) < self.restricted_height)
@ -93,91 +67,27 @@ class Animation(object):
admissibles = admissibles[:rgbs.shape[0]] admissibles = admissibles[:rgbs.shape[0]]
return points[admissibles, :], rgbs[admissibles, :] return points[admissibles, :], rgbs[admissibles, :]
def update(self): def update(self, alpha):
if self.nframes_past > self.nframes: if alpha < 0:
return False alpha = 0
self.nframes_past += 1 if alpha > 1:
for anim in self.concurrent_animations + self.reference_animations: alpha = 1
anim.update() self.update_mobject(self.alpha_func(alpha))
self.update_mobject(self.alpha_func(self.get_fraction_complete()))
return True
def while_also(self, action, display = True, *args, **kwargs): # def generate_frames(self):
if isinstance(action, type) and issubclass(action, Animation): # print "Generating " + str(self) + "..."
self.reference_animations += [ # progress_bar = progressbar.ProgressBar(maxval=self.nframes)
action(mobject, *args, **kwargs) # progress_bar.start()
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 with_background(self, *mobjects): # self.frames = []
for anim in [self] + self.following_animations: # while self.update():
anim.background_mobjects.append(CompoundMobject(*mobjects)) # self.frames.append(self.get_image())
return self # progress_bar.update(self.nframes_past - 1)
# self.clean_up()
def then(self, action, carry_over_background = False, *args, **kwargs): # for anim in self.following_animations:
if isinstance(action, type) and issubclass(action, Animation): # self.frames += anim.get_frames()
action = action(mobject = self.mobject, *args, **kwargs) # progress_bar.finish()
if carry_over_background: # return self
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
def filter_out(self, *filter_functions): def filter_out(self, *filter_functions):
self.filter_functions += filter_functions self.filter_functions += filter_functions
@ -193,22 +103,10 @@ class Animation(object):
def shift(self, vector): def shift(self, vector):
self.spacial_center += vector self.spacial_center += vector
for anim in self.following_animations:
anim.shift(vector)
return self return self
def set_dither(self, time, apply_to_concurrent = False): def set_run_time(self, time):
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):
self.run_time = 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() return self.reload()
def set_alpha_func(self, alpha_func): def set_alpha_func(self, alpha_func):
@ -218,30 +116,17 @@ class Animation(object):
return self return self
def set_name(self, name): def set_name(self, name):
self.inputted_name = name self.name = name
return self return self
def reload(self): # def drag_pixels(self):
self.nframes, self.ndither_frames = self.get_frame_count() # self.frames = drag_pixels(self.get_frames())
if self.frames: # return self
self.nframes_past = 0
self.generate_frames()
return self
def drag_pixels(self): # def reverse(self):
self.frames = drag_pixels(self.get_frames()) # self.get_frames().reverse()
return self # self.name = 'Reversed' + str(self)
# 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 update_mobject(self, alpha): def update_mobject(self, alpha):
#Typically ipmlemented by subclass #Typically ipmlemented by subclass
@ -250,9 +135,6 @@ class Animation(object):
def clean_up(self): def clean_up(self):
pass pass
def dither(self):
pass
###### Concrete Animations ######## ###### Concrete Animations ########
@ -263,12 +145,11 @@ class Rotating(Animation):
axes = [[0, 0, 1], [0, 1, 0]], axes = [[0, 0, 1], [0, 1, 0]],
radians = 2 * np.pi, radians = 2 * np.pi,
run_time = 20.0, run_time = 20.0,
dither_time = 0.0,
alpha_func = None, alpha_func = None,
*args, **kwargs): *args, **kwargs):
Animation.__init__( Animation.__init__(
self, mobject, self, mobject,
run_time = run_time, dither_time = dither_time, run_time = run_time,
alpha_func = alpha_func, alpha_func = alpha_func,
*args, **kwargs *args, **kwargs
) )
@ -282,19 +163,20 @@ class Rotating(Animation):
self.radians * alpha, self.radians * alpha,
axis axis
) )
class RotationAsTransform(Rotating): 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, run_time = DEFAULT_ANIMATION_RUN_TIME,
dither_time = DEFAULT_DITHER_TIME, alpha_func = high_inflection_0_to_1,
*args, **kwargs): *args, **kwargs):
Rotating.__init__( Rotating.__init__(
self, self,
mobject, mobject,
axis = (0, 0, 1), axis = axis,
axes = axes,
run_time = run_time, run_time = run_time,
dither_time = dither_time,
radians = radians, radians = radians,
alpha_func = high_inflection_0_to_1, alpha_func = alpha_func,
) )
class FadeOut(Animation): class FadeOut(Animation):
@ -309,7 +191,8 @@ class Reveal(Animation):
#TODO, Why do you need to do this? Shouldn't points always align? #TODO, Why do you need to do this? Shouldn't points always align?
class Transform(Animation): 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): *args, **kwargs):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
Mobject.align_data(mobject1, mobject2) Mobject.align_data(mobject1, mobject2)
@ -353,7 +236,7 @@ class Transform(Animation):
class ApplyMethod(Transform): class ApplyMethod(Transform):
def __init__(self, method, mobject, *args, **kwargs): def __init__(self, method, mobject, *args, **kwargs):
""" """
method is a method of Mobject Method is a method of Mobject
""" """
method_args = () method_args = ()
if isinstance(method, tuple): if isinstance(method, tuple):
@ -434,6 +317,7 @@ class ComplexHomotopy(Homotopy):
class ShowCreation(Animation): class ShowCreation(Animation):
def update_mobject(self, alpha): def update_mobject(self, alpha):
#TODO, shoudl I make this more efficient?
new_num_points = int(alpha * self.starting_mobject.points.shape[0]) new_num_points = int(alpha * self.starting_mobject.points.shape[0])
for attr in ["points", "rgbs"]: for attr in ["points", "rgbs"]:
setattr( setattr(
@ -444,10 +328,9 @@ class ShowCreation(Animation):
class Flash(Animation): class Flash(Animation):
def __init__(self, mobject, color = "white", slow_factor = 0.01, 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): *args, **kwargs):
Animation.__init__(self, mobject, run_time = run_time, Animation.__init__(self, mobject, run_time = run_time,
dither_time = dither_time,
alpha_func = alpha_func, alpha_func = alpha_func,
*args, **kwargs) *args, **kwargs)
self.intermediate = Mobject(color = color) self.intermediate = Mobject(color = color)

View file

@ -8,7 +8,7 @@ DEFAULT_POINT_DENSITY_1D = 200 if PRODUCTION_QUALITY else 50
HEIGHT = 1024#1440 if PRODUCTION_QUALITY else 480 HEIGHT = 1024#1440 if PRODUCTION_QUALITY else 480
WIDTH = 1024#2560 if PRODUCTION_QUALITY else 640 WIDTH = 1024#2560 if PRODUCTION_QUALITY else 640
#All in seconds #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_ANIMATION_RUN_TIME = 3.0
DEFAULT_TRANSFORM_RUN_TIME = 1.0 DEFAULT_TRANSFORM_RUN_TIME = 1.0
DEFAULT_DITHER_TIME = 1.0 DEFAULT_DITHER_TIME = 1.0

View file

@ -36,20 +36,21 @@ def get_pixels(points, rgbs):
pixels[indices] = rgbs pixels[indices] = rgbs
return pixels.reshape((HEIGHT, WIDTH, 3)).astype('uint8') 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 #TODO, find better means of compression
if not name.endswith(".gif"): if not name.endswith(".gif"):
name += ".gif" name += ".gif"
filepath = os.path.join(GIF_DIR, name) filepath = os.path.join(GIF_DIR, name)
temppath = os.path.join(GIF_DIR, "Temp.gif") temppath = os.path.join(GIF_DIR, "Temp.gif")
print "Writing " + name + "..." print "Writing " + name + "..."
writeGif(temppath, animation.get_frames(), animation.pause_time) writeGif(temppath, scene.frames, scene.frame_duration)
print "Compressing..." print "Compressing..."
os.system("gifsicle -O " + temppath + " > " + filepath) os.system("gifsicle -O " + temppath + " > " + filepath)
os.system("rm " + temppath) os.system("rm " + temppath)
def write_to_movie(animation, name): def write_to_movie(scene, name):
frames = animation.get_frames() #TODO, incorporate pause time
frames = scene.frames
progress_bar = progressbar.ProgressBar(maxval=len(frames)) progress_bar = progressbar.ProgressBar(maxval=len(frames))
progress_bar.start() progress_bar.start()
print "writing " + name + "..." print "writing " + name + "..."
@ -75,7 +76,7 @@ def write_to_movie(animation, name):
"-c:v", "-c:v",
"libx264", "libx264",
"-vf", "-vf",
"fps=%d,format=yuv420p"%int(1/animation.pause_time), "fps=%d,format=yuv420p"%int(1/scene.frame_duration),
filepath filepath
] ]
os.system(" ".join(commands)) os.system(" ".join(commands))
@ -87,7 +88,7 @@ def write_to_movie(animation, name):
# filepath = os.path.join(MOVIE_DIR, name + ".mov") # filepath = os.path.join(MOVIE_DIR, name + ".mov")
# fourcc = cv2.cv.FOURCC(*"8bps") # fourcc = cv2.cv.FOURCC(*"8bps")
# out = cv2.VideoWriter( # 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 # progress = 0
# for frame in frames: # for frame in frames:

80
scene.py Normal file
View file

@ -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))