diff --git a/constants.py b/constants.py index 909e1f62..86bf211a 100644 --- a/constants.py +++ b/constants.py @@ -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 + + + + + + + + + + + + + + + + diff --git a/helpers.py b/helpers.py index 36d5a64a..3066c0b8 100644 --- a/helpers.py +++ b/helpers.py @@ -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) diff --git a/mobject/creatures.py b/mobject/creatures.py index b411d034..c0ac3a68 100644 --- a/mobject/creatures.py +++ b/mobject/creatures.py @@ -7,108 +7,95 @@ 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 = [ - 'arm', - 'body', - 'left_eye', - 'right_eye', - 'left_leg', - 'right_leg', - 'mouth', - ] - white_part_names = ['left_eye', 'right_eye', 'mouth'] - directory = os.path.join(IMAGE_DIR, "PiCreature") + PART_NAMES = [ + 'arm', + 'body', + 'left_eye', + 'right_eye', + 'left_leg', + 'right_leg', + 'mouth', + ] + 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: + 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) - 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) - ) 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() diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index c3e93725..cd1e310d 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -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 - 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] - ) - possible_paths = [ - image, - os.path.join(IMAGE_DIR, image), - os.path.join(IMAGE_DIR, image + ".jpg"), - os.path.join(IMAGE_DIR, image + ".png"), - ] - found = False - for path in possible_paths: - if os.path.exists(path): - image = Image.open(path).convert('RGB') - found = True - if not found: - raise IOError("File not Found") - if invert: - image = invert_image(image) - self.image_array = np.array(image) Mobject2D.__init__(self, *args, **kwargs) + self.filter_rgb = 255 * np.array(Color(filter_color).get_rgb()).astype('uint8') + self.name = to_cammel_case( + os.path.split(image_file)[-1].split(".")[0] + ) + possible_paths = [ + 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"), + ] + for path in possible_paths: + if os.path.exists(path): + self.generate_points_from_file(path, invert) + return + raise IOError("File not Found") - def generate_points(self): - height, width = self.image_array.shape[:2] + 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.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_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() diff --git a/mobject/mobject.py b/mobject/mobject.py index c65d593e..d21ec525 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -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 + + + + + + + + + + + + + + + + + + + + diff --git a/mobject/simple_mobjects.py b/mobject/simple_mobjects.py index 33647e3f..9ff18f4a 100644 --- a/mobject/simple_mobjects.py +++ b/mobject/simple_mobjects.py @@ -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.via = via + ) + 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) ]) diff --git a/sample_script.py b/sample_script.py index 205f0ce3..db6736e7 100644 --- a/sample_script.py +++ b/sample_script.py @@ -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() \ No newline at end of file diff --git a/tex_utils.py b/tex_utils.py index 583761f9..bdceb35b 100644 --- a/tex_utils.py +++ b/tex_utils.py @@ -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):