Better creatures and image mobject caching

This commit is contained in:
Grant Sanderson
2015-08-17 11:12:56 -07:00
parent 5fd08cddbb
commit 7087899f12
8 changed files with 308 additions and 262 deletions

View File

@ -51,8 +51,12 @@ GIF_DIR = os.path.join(FILE_DIR, "gifs")
MOVIE_DIR = os.path.join(FILE_DIR, "movies")
TEX_DIR = os.path.join(FILE_DIR, "Tex")
TEX_IMAGE_DIR = os.path.join(IMAGE_DIR, "Tex")
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
TMP_IMAGE_DIR = "/tmp/animation_images/"
for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR, TMP_IMAGE_DIR, TEX_IMAGE_DIR]:
for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR, TMP_IMAGE_DIR,
TEX_IMAGE_DIR, MOBJECT_DIR, IMAGE_MOBJECT_DIR]:
if not os.path.exists(folder):
os.mkdir(folder)
@ -69,5 +73,25 @@ DARK_BLUE = "#236B8E"
DARK_BROWN = "#8B4513"
LIGHT_BROWN = "#CD853F"
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
PI_CREATURE_PART_NAME_TO_DIR = lambda name : os.path.join(PI_CREATURE_DIR, "pi_creature_"+name) + ".png"
PI_CREATURE_SCALE_VAL = 0.5
PI_CREATURE_MOUTH_TO_EYES_DISTANCE = 0.25

View File

@ -9,6 +9,9 @@ import operator as op
from constants import *
def interpolate(start, end, alpha):
return (1-alpha)*start + alpha*end
def center_of_mass(points):
points = [np.array(point).astype("float") for point in points]
return sum(points) / len(points)

View File

@ -7,14 +7,9 @@ from mobject import *
from simple_mobjects import *
class PiCreature(Mobject):
class PiCreature(CompoundMobject):
DEFAULT_COLOR = "blue"
def __init__(self, **kwargs):
Mobject.__init__(self, **kwargs)
color = self.DEFAULT_COLOR
scale_val = 0.5
mouth_to_eyes_distance = 0.25
part_names = [
PART_NAMES = [
'arm',
'body',
'left_eye',
@ -23,92 +18,84 @@ class PiCreature(Mobject):
'right_leg',
'mouth',
]
white_part_names = ['left_eye', 'right_eye', 'mouth']
directory = os.path.join(IMAGE_DIR, "PiCreature")
WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
MOUTH_NAMES = ["smile", "frown", "straight_mouth"]
parts = []
self.white_parts = []
for part_name in part_names:
path = os.path.join(directory, "pi_creature_"+part_name)
path += ".png"
mob = ImageMobject(path)
mob.scale(scale_val)
if part_name in white_part_names:
self.white_parts.append(mob)
else:
mob.highlight(color)
setattr(self, part_name, mob)
parts.append(mob)
self.mouth.center().shift(
self.left_eye.get_center()/2 +
self.right_eye.get_center()/2 -
(0, mouth_to_eyes_distance, 0)
def __init__(self, **kwargs):
color = self.DEFAULT_COLOR if "color" not in kwargs else kwargs.pop("color")
for part_name in self.PART_NAMES:
mob = ImageMobject(
PI_CREATURE_PART_NAME_TO_DIR(part_name)
)
if part_name not in self.WHITE_PART_NAMES:
mob.highlight(color)
mob.scale(PI_CREATURE_SCALE_VAL)
setattr(self, part_name, mob)
self.eyes = [self.left_eye, self.right_eye]
self.legs = [self.left_leg, self.right_leg]
for part in parts:
self.add(part)
self.parts = parts
mouth_center = self.get_mouth_center()
self.mouth.center()
self.smile = deepcopy(self.mouth)
self.frown = deepcopy(self.mouth).rotate(np.pi, RIGHT)
self.straight_mouth = tex_mobject("-").scale(0.5)
for mouth_name in ["mouth"] + self.MOUTH_NAMES:
mouth = getattr(self, mouth_name)
mouth.sort_points(lambda p : p[0])
mouth.shift(mouth_center)
#Ordering matters here, so hidden mouths are behind body
self.part_names = self.MOUTH_NAMES + self.PART_NAMES
self.white_parts = self.MOUTH_NAMES + self.WHITE_PART_NAMES
CompoundMobject.__init__(
self,
*self.get_parts(),
**kwargs
)
def rewire_part_attributes(self, self_from_parts = False):
if self_from_parts:
total_num_points = sum(map(Mobject.get_num_points, self.parts))
self.points = np.zeros((total_num_points, Mobject.DIM))
self.rgbs = np.zeros((total_num_points, Mobject.DIM))
curr = 0
for part in self.parts:
n_points = part.get_num_points()
if self_from_parts:
self.points[curr:curr+n_points,:] = part.points
self.rgbs[curr:curr+n_points,:] = part.rgbs
else:
part.points = self.points[curr:curr+n_points,:]
part.rgbs = self.rgbs[curr:curr+n_points,:]
curr += n_points
def sync_parts(self):
CompoundMobject.__init__(self, *self.get_parts())
return self
def reload_from_parts(self):
self.rewire_part_attributes(self_from_parts = True)
def TODO_what_should_I_do_with_this(self):
for part_name, mob in zip(self.part_names, self.split()):
setattr(self, part_name, mob)
def get_parts(self):
return [getattr(self, pn) for pn in self.part_names]
def get_white_parts(self):
return [getattr(self, pn) for pn in self.white_parts]
def get_mouth_center(self):
left_center = self.left_eye.get_center()
right_center = self.right_eye.get_center()
l_to_r = right_center-left_center
eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT)
eyes_to_mouth /= np.linalg.norm(eyes_to_mouth)
return left_center/2 + right_center/2 + \
PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth
def highlight(self, color, condition = None):
self.rewire_part_attributes()
if condition is not None:
Mobject.highlight(self, color, condition)
return self
for part in set(self.parts).difference(self.white_parts):
part.highlight(color)
self.reload_from_parts()
return self
for part in set(self.get_parts()).difference(self.get_white_parts()):
part.highlight(color, condition)
return self.sync_parts()
def move_to(self, destination):
bottom = np.array((
np.mean(self.points[:,0]),
min(self.points[:,1]),
0
))
self.shift(destination-bottom)
self.rewire_part_attributes()
return self
self.shift(destination-self.get_bottom())
return self.sync_parts()
def change_mouth_to(self, mouth_name):
self.mouth = getattr(self, mouth_name)
return self.sync_parts()
def give_smile(self):
return self.change_mouth_to("smile")
def give_frown(self):
center = self.mouth.get_center()
self.mouth.center()
self.mouth.apply_function(lambda (x, y, z) : (x, -y, z))
self.mouth.shift(center)
self.reload_from_parts()
return self
return self.change_mouth_to("frown")
def give_straight_face(self):
center = self.mouth.get_center()
self.mouth.center()
new_mouth = tex_mobject("-").scale(0.5)
new_mouth.center().shift(self.mouth.get_center())
new_mouth.shift(center)
self.parts[self.parts.index(self.mouth)] = new_mouth
self.white_parts[self.white_parts.index(self.mouth)] = new_mouth
self.mouth = new_mouth
self.reload_from_parts()
return self
return self.change_mouth_to("straight_mouth")
def get_eye_center(self):
return center_of_mass([
@ -123,7 +110,7 @@ class PiCreature(Mobject):
for eye in self.left_eye, self.right_eye:
eye.highlight("black", should_delete)
self.give_straight_face()
return self
return self.sync_parts()
def make_sad(self):
eye_x, eye_y = self.get_eye_center()[:2]
@ -133,13 +120,11 @@ class PiCreature(Mobject):
for eye in self.left_eye, self.right_eye:
eye.highlight("black", should_delete)
self.give_frown()
self.reload_from_parts()
return self
return self.sync_parts()
def get_step_intermediate(self, pi_creature):
vect = pi_creature.get_center() - self.get_center()
result = deepcopy(self).shift(vect / 2.0)
result.rewire_part_attributes()
left_forward = vect[0] > 0
if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]:
#For Mortimer's case
@ -150,31 +135,26 @@ class PiCreature(Mobject):
else:
result.right_leg.wag(vect/2.0, DOWN)
result.left_leg.wag(-vect/2.0, DOWN)
return result
return result.sync_parts()
def blink(self):
for eye in self.left_eye, self.right_eye:
bottom = min(eye.points[:,1])
bottom = eye.get_bottom()
eye.apply_function(
lambda (x, y, z) : (x, bottom, z)
lambda (x, y, z) : (x, bottom[1], z)
)
self.reload_from_parts()
return self
return self.sync_parts()
def shift_eyes(self):
for eye in self.left_eye, self.right_eye:
center = eye.get_center()
eye.center()
eye.rotate(np.pi, UP)
eye.shift(center)
self.reload_from_parts()
return self
eye.rotate_in_place(np.pi, UP)
return self.sync_parts()
def to_symbol(self):
for white_part in self.white_parts:
self.parts.remove(white_part)
self.reload_from_parts()
return self
CompoundMobject.__init__(
self,
*list(set(self.get_parts()).difference(self.get_white_parts()))
)
class Randolph(PiCreature):
@ -187,18 +167,6 @@ class Mortimer(PiCreature):
# self.highlight(DARK_BROWN)
self.give_straight_face()
self.rotate(np.pi, UP)
self.rewire_part_attributes()
class TauCreature(PiCreature):
def __init__(self, **kwargs):
leg_shift_val = 0.25
leg_wag_val = 0.2
PiCreature.__init__(self, **kwargs)
self.parts.remove(self.right_leg)
self.left_leg.shift(leg_shift_val*RIGHT)
self.left_leg.wag(leg_wag_val*RIGHT, DOWN)
self.leg = self.left_leg
self.reload_from_parts()

View File

@ -13,42 +13,56 @@ class ImageMobject(Mobject2D):
"""
# SHOULD_BUFF_POINTS = False
def __init__(self,
image,
image_file,
filter_color = "black",
invert = True,
*args, **kwargs):
#TODO, Make sure you always convert to RGB
Mobject2D.__init__(self, *args, **kwargs)
self.filter_rgb = 255 * np.array(Color(filter_color).get_rgb()).astype('uint8')
if isinstance(image, str):
self.name = to_cammel_case(
os.path.split(image)[-1].split(".")[0]
os.path.split(image_file)[-1].split(".")[0]
)
possible_paths = [
image,
os.path.join(IMAGE_DIR, image),
os.path.join(IMAGE_DIR, image + ".jpg"),
os.path.join(IMAGE_DIR, image + ".png"),
image_file,
os.path.join(IMAGE_DIR, image_file),
os.path.join(IMAGE_DIR, image_file + ".jpg"),
os.path.join(IMAGE_DIR, image_file + ".png"),
]
found = False
for path in possible_paths:
if os.path.exists(path):
image = Image.open(path).convert('RGB')
found = True
if not found:
self.generate_points_from_file(path, invert)
return
raise IOError("File not Found")
def generate_points_from_file(self, path, invert):
#Hash should be unique to (path, invert) pair
dtype = 'float'
unique_hash = str(hash(path+str(invert)))
cached_points, cached_rgbs = [
os.path.join(IMAGE_MOBJECT_DIR, unique_hash)+extension
for extension in ".points", ".rgbs"
]
if os.path.exists(cached_points) and os.path.exists(cached_rgbs):
self.points = np.fromfile(cached_points, dtype = dtype)
self.rgbs = np.fromfile(cached_rgbs, dtype = dtype)
n_points = self.points.size/self.DIM
self.points = self.points.reshape(n_points, self.DIM)
self.rgbs = self.rgbs.reshape(n_points, 3)
else:
image = Image.open(path).convert('RGB')
if invert:
image = invert_image(image)
self.image_array = np.array(image)
Mobject2D.__init__(self, *args, **kwargs)
self.generate_points_from_image_array(np.array(image))
self.points.astype(dtype).tofile(cached_points)
self.rgbs.astype(dtype).tofile(cached_rgbs)
def generate_points(self):
height, width = self.image_array.shape[:2]
def generate_points_from_image_array(self, image_array):
height, width = image_array.shape[:2]
#Flatten array, and find indices where rgb is not filter_rgb
array = self.image_array.reshape((height * width, 3))
ones = np.ones(height * width, dtype = 'bool')
for i in range(3):
ones *= (array[:,i] != self.filter_rgb[i])
indices = np.arange(height * width, dtype = 'int')[ones]
array = image_array.reshape((height * width, 3))
bools = array == self.filter_rgb
bools = bools[:,0]*bools[:,1]*bools[:,2]
indices = np.arange(height * width, dtype = 'int')[~bools]
rgbs = array[indices, :].astype('float') / 255.0
points = np.zeros((indices.size, 3), dtype = 'float64')
@ -94,12 +108,12 @@ def tex_mobject(expression,
else:
size = "\\large"
#Todo, make this more sophisticated.
images = tex_to_image(expression, size, template_tex_file)
if isinstance(images, list):
image_files = tex_to_image(expression, size, template_tex_file)
if isinstance(image_files, list):
#TODO, is checking listiness really the best here?
result = CompoundMobject(*map(ImageMobject, images))
result = CompoundMobject(*map(ImageMobject, image_files))
else:
result = ImageMobject(images)
result = ImageMobject(image_files)
return result.highlight("white").center()

View File

@ -5,6 +5,7 @@ from PIL import Image
from random import random
from copy import deepcopy
from colour import Color
import inspect
from constants import *
from helpers import *
@ -17,10 +18,9 @@ class Mobject(object):
"""
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
DIM = 3
DEFAULT_COLOR = Color("skyblue")
SHOULD_BUFF_POINTS = GENERALLY_BUFF_POINTS
EDGE_BUFFER = 0.5
def __init__(self,
color = None,
@ -74,14 +74,26 @@ class Mobject(object):
).reshape(self.points.shape)
return self
def rotate(self, angle, axis = [0, 0, 1]):
def add(self, *mobjects):
for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs)
return self
def repeat(self, count):
#Can make transition animations nicer
points, rgbs = deepcopy(self.points), deepcopy(self.rgbs)
for x in range(count - 1):
self.add_points(points, rgbs)
return self
def rotate(self, angle, axis = OUT):
t_rotation_matrix = np.transpose(rotation_matrix(angle, axis))
self.points = np.dot(self.points, t_rotation_matrix)
if self.has_normals:
self.unit_normals = np.dot(self.unit_normals, t_rotation_matrix)
return self
def rotate_in_place(self, angle, axis = (0, 0, 1)):
def rotate_in_place(self, angle, axis = OUT):
center = self.get_center()
self.shift(-center)
self.rotate(angle, axis)
@ -89,12 +101,7 @@ class Mobject(object):
return self
def shift(self, vector):
cycle = it.cycle(vector)
v = np.array([
cycle.next()
for x in range(self.points.size)
]).reshape(self.points.shape)
self.points += v
self.points += vector
return self
def wag(self, wag_direction = RIGHT, wag_axis = DOWN,
@ -105,7 +112,7 @@ class Mobject(object):
alphas = alphas**wag_factor
self.points += np.dot(
alphas.reshape((len(alphas), 1)),
np.array(wag_direction).reshape((1, 3))
np.array(wag_direction).reshape((1, self.DIM))
)
return self
@ -114,18 +121,18 @@ class Mobject(object):
return self
#Wrapper functions for better naming
def to_corner(self, corner = (-1, 1, 0), buff = 0.5):
def to_corner(self, corner = LEFT+DOWN, buff = EDGE_BUFFER):
return self.align_on_border(corner, buff)
def to_edge(self, edge = (-1, 0, 0), buff = 0.5):
def to_edge(self, edge = LEFT, buff = EDGE_BUFFER):
return self.align_on_border(edge, buff)
def align_on_border(self, direction, buff = 0.5):
def align_on_border(self, direction, buff = EDGE_BUFFER):
"""
Direction just needs to be a vector pointing towards side or
corner in the 2d plane.
"""
shift_val = [0, 0, 0]
shift_val = ORIGIN
space_dim = (SPACE_WIDTH, SPACE_HEIGHT)
for i in [0, 1]:
if direction[i] == 0:
@ -137,7 +144,6 @@ class Mobject(object):
self.shift(shift_val)
return self
def scale(self, scale_factor):
self.points *= scale_factor
return self
@ -164,18 +170,6 @@ class Mobject(object):
def stretch_to_fit_height(self, height):
return self.stretch_to_fit(height, 1)
def add(self, *mobjects):
for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs)
return self
def repeat(self, count):
#Can make transition animations nicer
points, rgbs = deepcopy(self.points), deepcopy(self.rgbs)
for x in range(count - 1):
self.add_points(points, rgbs)
return self
def pose_at_angle(self):
self.rotate(np.pi / 7)
self.rotate(np.pi / 7, [1, 0, 0])
@ -229,10 +223,12 @@ class Mobject(object):
"""
function is any map from R^3 to R
"""
self.points = np.array(sorted(
self.points,
lambda *points : cmp(*map(function, points))
))
indices = range(self.get_num_points())
indices.sort(
lambda *pair : cmp(*map(function, self.points[pair, :]))
)
self.points = self.points[indices]
self.rgbs = self.rgbs[indices]
return self
### Getters ###
@ -249,22 +245,24 @@ class Mobject(object):
def get_boundary_point(self, direction):
return self.points[np.argmax(np.dot(self.points, direction))]
def get_edge_center(self, dim, max_or_min_func):
def get_edge_center(self, direction):
dim = np.argmax(map(abs, direction))
max_or_min_func = np.max if direction[dim] > 0 else np.min
result = self.get_center()
result[dim] = max_or_min_func(self.points[:,dim])
return result
def get_top(self):
return self.get_edge_center(1, np.max)
return self.get_edge_center(UP)
def get_bottom(self):
return self.get_edge_center(1, np.min)
return self.get_edge_center(DOWN)
def get_right(self):
return self.get_edge_center(0, np.max)
return self.get_edge_center(RIGHT)
def get_left(self):
return self.get_edge_center(0, np.min)
return self.get_edge_center(LEFT)
def get_width(self):
return np.max(self.points[:, 0]) - np.min(self.points[:, 0])
@ -341,4 +339,60 @@ class CompoundMobject(Mobject):
curr += num_points
return result
# class CompoundMobject(Mobject):
# """
# Treats a collection of mobjects as if they were one.
# A weird form of inhertance is at play here...
# """
# def __init__(self, *mobjects):
# Mobject.__init__(self)
# self.mobjects = mobjects
# name_to_method = dict(
# inspect.getmembers(Mobject, predicate = inspect.ismethod)
# )
# names = name_to_method.keys()
# #Most reductions take the form of mapping a given method across
# #all constituent mobjects, then just returning self.
# name_to_reduce = dict([
# (name, lambda list : self)
# for name in names
# ])
# name_to_reduce.update(self.get_special_reduce_functions())
# def make_pseudo_method(name):
# return lambda *args, **kwargs : name_to_reduce[name]([
# name_to_method[name](mob, *args, **kwargs)
# for mob in self.mobjects
# ])
# for name in names:
# setattr(self, name, make_pseudo_method(name))
# def show(self):
# def get_special_reduce_functions(self):
# return {}
# def handle_method(self, method_name, *args, **kwargs):
# pass

View File

@ -6,66 +6,22 @@ from image_mobject import text_mobject
from constants import *
from helpers import *
class Point(Mobject):
DEFAULT_COLOR = "black"
def __init__(self, point = (0, 0, 0), *args, **kwargs):
def __init__(self, location = ORIGIN, *args, **kwargs):
self.location = np.array(location)
Mobject.__init__(self, *args, **kwargs)
self.points = np.array(point).reshape(1, 3)
self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3)
class Arrow(Mobject1D):
DEFAULT_COLOR = "white"
DEFAULT_NUDGE_DISTANCE = 0.1
def __init__(self,
point = (0, 0, 0),
direction = (-1, 1, 0),
tail = None,
length = 1,
tip_length = 0.25,
normal = (0, 0, 1),
density = DEFAULT_POINT_DENSITY_1D,
*args, **kwargs):
self.point = np.array(point)
if tail is not None:
direction = self.point - tail
length = np.linalg.norm(direction)
self.direction = np.array(direction) / np.linalg.norm(direction)
density *= max(length, 0.1)
self.length = length
self.normal = np.array(normal)
self.tip_length = tip_length
Mobject1D.__init__(self, density = density, **kwargs)
def generate_points(self):
self.add_points([
[x, x, x] * self.direction + self.point
for x in np.arange(-self.length, 0, self.epsilon)
])
tips_dir = [np.array(-self.direction), np.array(-self.direction)]
for i, sgn in zip([0, 1], [-1, 1]):
tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
self.add_points([
[x, x, x] * tips_dir[i] + self.point
for x in np.arange(0, self.tip_length, self.epsilon)
for i in [0, 1]
])
self.add_points(self.location.reshape((1, 3)))
def nudge(self, distance = None):
if distance is None:
distance = self.DEFAULT_NUDGE_DISTANCE
return self.shift(-self.direction * distance)
class Vector(Arrow):
def __init__(self, point = (1, 0, 0), *args, **kwargs):
length = np.linalg.norm(point)
Arrow.__init__(self, point = point, direction = point,
length = length, tip_length = 0.2 * length,
*args, **kwargs)
class Dot(Mobject1D): #Use 1D density, even though 2D
DEFAULT_COLOR = "white"
DEFAULT_RADIUS = 0.05
def __init__(self, center = (0, 0, 0), radius = DEFAULT_RADIUS, *args, **kwargs):
def __init__(self, center = ORIGIN, radius = DEFAULT_RADIUS,
*args, **kwargs):
center = np.array(center)
if center.size == 1:
raise Exception("Center must have 2 or 3 coordinates!")
@ -94,15 +50,32 @@ class Cross(Mobject1D):
])
class Line(Mobject1D):
def __init__(self, start, end, density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs):
self.start = np.array(start)
self.end = np.array(end)
density *= max(self.get_length(), 0.1)
MIN_DENSITY = 0.1
def __init__(self, start, end, density = DEFAULT_POINT_DENSITY_1D,
*args, **kwargs):
self.set_start_and_end(start, end)
density *= max(self.get_length(), self.MIN_DENSITY)
Mobject1D.__init__(self, density = density, *args, **kwargs)
def set_start_and_end(self, start, end):
preliminary_start, preliminary_end = [
arg.get_center()
if isinstance(arg, Mobject)
else np.array(arg)
for arg in start, end
]
start_to_end = preliminary_end - preliminary_start
longer_dim = np.argmax(map(abs, start_to_end))
self.start, self.end = [
arg.get_edge_center(unit*start_to_end)
if isinstance(arg, Mobject)
else np.array(arg)
for arg, unit in zip([start, end], [1, -1])
]
def generate_points(self):
self.add_points([
t * self.end + (1 - t) * self.start
interpolate(self.start, self.end, t)
for t in np.arange(0, 1, self.epsilon)
])
@ -116,7 +89,7 @@ class Line(Mobject1D):
]
return rise/run
class NewArrow(Line):
class Arrow(Line):
DEFAULT_COLOR = "white"
DEFAULT_TIP_LENGTH = 0.25
def __init__(self, *args, **kwargs):
@ -128,22 +101,38 @@ class NewArrow(Line):
self.add_tip(tip_length)
def add_tip(self, tip_length):
pass
vect = self.start-self.end
vect *= tip_length/np.linalg.norm(vect)
self.add_points([
interpolate(self.end, self.end+v, t)
for t in np.arange(0, 1, tip_length*self.epsilon)
for v in [
rotate_vector(vect, np.pi/4, axis)
for axis in IN, OUT
]
])
class CurvedLine(Line):
def __init__(self, start, end, via = None, *args, **kwargs):
self.set_start_and_end(start, end)
if via == None:
via = rotate_vector(
end - start,
self.via = rotate_vector(
self.end - self.start,
np.pi/3, [0,0,1]
) + start
) + self.start
elif isinstance(via, Mobject):
self.via = via.get_center()
else:
self.via = via
Line.__init__(self, start, end, *args, **kwargs)
def generate_points(self):
self.add_points([
4*(0.25-t*(1-t))*(t*self.end + (1-t)*self.start) +
4*t*(1-t)*self.via
interpolate(
interpolate(self.start, self.end, t),
self.via,
t*(1-t)
)
for t in np.arange(0, 1, self.epsilon)
])

View File

@ -10,20 +10,14 @@ from animation import *
from mobject import *
from constants import *
from region import *
from scene import Scene, RearrangeEquation
from scene import Scene
from script_wrapper import command_line_create_scene
class SampleScene(RearrangeEquation):
class SampleScene(Scene):
def construct(self):
three = tex_mobject("3")
three.sort_points(np.linalg.norm)
self.animate(DelayByOrder(ApplyMethod(three.scale, 3)))
self.dither()
self.add(three)
if __name__ == "__main__":
command_line_create_scene()

View File

@ -19,7 +19,7 @@ def tex_to_image(expression,
image_dir = os.path.join(TEX_IMAGE_DIR, exp_hash)
if os.path.exists(image_dir):
result = [
Image.open(os.path.join(image_dir, png_file)).convert('RGB')
os.path.join(image_dir, png_file)
for png_file in sorted(
os.listdir(image_dir),
cmp_enumerated_files
@ -92,7 +92,7 @@ def dvi_to_png(filename, regen_if_exists = False):
if name.endswith(".png")
]
image_paths.sort(cmp_enumerated_files)
return [Image.open(path).convert('RGB') for path in image_paths]
return image_paths
raise IOError("File not Found")
def cmp_enumerated_files(name1, name2):