First implementation of Camera to replace disp

This commit is contained in:
Grant Sanderson
2016-02-23 22:29:32 -08:00
parent 19e3c7f849
commit c4c342c406
5 changed files with 419 additions and 269 deletions

200
camera.py Normal file
View File

@ -0,0 +1,200 @@
import numpy as np
import itertools as it
import os
import sys
from PIL import Image
import cv2
from colour import Color
import progressbar
from helpers import *
class Camera(object):
DEFAULT_CONFIG = {
#background of a different shape will overwrite these
"pixel_width" : DEFAULT_WIDTH,
"pixel_height" : DEFAULT_HEIGHT,
"background_color" : BLACK,
#
"space_height" : SPACE_HEIGHT,
"space_center" : ORIGIN,
}
def __init__(self, background = None, **kwargs):
digest_config(self, kwargs, locals())
self.init_background()
self.reset()
width_to_height = float(self.pixel_width) / self.pixel_height
self.space_width = self.space_height * width_to_height
def init_background(self):
if self.background:
shape = self.background.shape[:2]
self.pixel_height, self.pixel_width = shape
else:
background_color = Color(self.background_color)
background_rgb = (255*np.array(
background_color.get_rgb()
)).astype('uint8')
ones = np.ones(
(self.pixel_height, self.pixel_width, 1),
dtype = 'uint8'
)
self.background = np.dot(
ones, background_rgb.reshape((1, 3))
)
def get_image(self):
return np.array(self.pixel_array)
def set_image(self, pixel_array):
self.pixel_array = np.array(pixel_array)
def reset(self):
self.set_image(np.array(self.background))
# def paint_region(region, image_array = None, color = None):
# pixels = get_pixels(image_array)
# assert region.shape == pixels.shape[:2]
# if color is None:
# #Random dark color
# rgb = 0.5 * np.random.random(3)
# else:
# rgb = np.array(Color(color).get_rgb())
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
# return pixels
def capture_mobject(self, mobject):
return self.capture_mobjects([mobject])
def capture_mobjects(self, mobjects, include_sub_mobjects = True):
if include_sub_mobjects:
all_families = [
mob.submobject_family()
for mob in mobjects
]
mobjects = reduce(op.add, all_families, [])
for mobject in mobjects:
self.display_points(
mobject.points, mobject.rgbs,
self.adjusted_thickness(mobject.point_thickness)
)
def display_points(self, points, rgbs, thickness):
if len(points) == 0:
return
points = self.align_points_to_camera(points)
pixel_coords = self.points_to_pixel_coords(points)
pixel_coords = self.thickened_coordinates(
pixel_coords, thickness
)
rgbs = (255*rgbs).astype('uint8')
target_len = len(pixel_coords)
factor = target_len/len(rgbs)
rgbs = np.array([rgbs]*factor).reshape((target_len, 3))
on_screen_indices = self.on_screen_pixels(pixel_coords)
pixel_coords = pixel_coords[on_screen_indices]
rgbs = rgbs[on_screen_indices]
flattener = np.array([1, self.pixel_width], dtype = 'int')
flattener = flattener.reshape((2, 1))
indices = np.dot(pixel_coords, flattener)[:,0]
indices = indices.astype('int')
pw, ph = self.pixel_width, self.pixel_height
# new_array = np.zeros((pw*ph, 3), dtype = 'uint8')
# new_array[indices, :] = rgbs
new_pa = self.pixel_array.reshape((ph*pw, 3))
new_pa[indices] = rgbs
self.pixel_array = new_pa.reshape((ph, pw, 3))
def align_points_to_camera(self, points):
## This is where projection should live
return points - self.space_center
def points_to_pixel_coords(self, points):
result = np.zeros((len(points), 2))
width_mult = self.pixel_width/self.space_width/2
width_add = self.pixel_width/2
height_mult = self.pixel_height/self.space_height/2
height_add = self.pixel_height/2
#Flip on y-axis as you go
height_mult *= -1
result[:,0] = points[:,0]*width_mult + width_add
result[:,1] = points[:,1]*height_mult + height_add
return result.astype('int')
def on_screen_pixels(self, pixel_coords):
return reduce(op.and_, [
pixel_coords[:,0] >= 0,
pixel_coords[:,0] < self.pixel_width,
pixel_coords[:,1] >= 0,
pixel_coords[:,1] < self.pixel_height,
])
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
# """
# Imagine dragging each pixel around like a paintbrush in
# a plus-sign-shaped pixel arrangement surrounding it.
# Pass rgb = None to do nothing to them
# """
# thickness = adjusted_thickness(thickness, width, height)
# original = np.array(pixel_indices_and_rgbs)
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
# for nudge in range(-thickness/2+1, thickness/2+1):
# if nudge == 0:
# continue
# for x, y in [[nudge, 0], [0, nudge]]:
# pixel_indices_and_rgbs = np.append(
# pixel_indices_and_rgbs,
# original+([x, y] + [0]*n_extra_columns),
# axis = 0
# )
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
# (pixel_indices_and_rgbs[:,0] < width) & \
# (pixel_indices_and_rgbs[:,1] >= 0) & \
# (pixel_indices_and_rgbs[:,1] < height)
# return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(self, thickness):
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
factor = (big_width + big_height) / \
(self.pixel_width + self.pixel_height)
return 1 + (thickness-1)/factor
def get_thickening_nudges(self, thickness):
_range = range(-thickness/2+1, thickness/2+1)
return np.array(list(it.product(*[_range]*2)))
def thickened_coordinates(self, pixel_coords, thickness):
nudges = self.get_thickening_nudges(thickness)
pixel_coords = np.array([
pixel_coords + nudge
for nudge in nudges
])
size = pixel_coords.size
return pixel_coords.reshape((size/2, 2))

View File

@ -84,6 +84,8 @@ MAX_LEN_FOR_HUGE_TEX_FONT = 25
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
FFMPEG_BIN = "ffmpeg"
### Colors ###

View File

@ -1,6 +1,5 @@
import numpy as np
import itertools as it
import subprocess as sp
import os
import sys
from PIL import Image
@ -10,40 +9,76 @@ import progressbar
from helpers import *
FFMPEG_BIN = "ffmpeg"
class Camera(object):
DEFAULT_CONFIG = {
"pixel_width" : DEFAULT_WIDTH,
"pixel_height" : DEFAULT_HEIGHT,
"space_height" : SPACE_HEIGHT,
"space_center" : ORIGIN,
"background_color" : BLACK,
}
def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE
if image_array is None:
return np.zeros(
(DEFAULT_HEIGHT, DEFAULT_WIDTH, 3),
def __init__(self, background = None, **kwargs):
digest_config(self, kwargs, locals())
self.init_background()
self.reset()
width_to_height = float(self.pixel_width) / self.pixel_height
self.space_width = self.space_height * width_to_height
def init_background(self):
if self.background:
shape = self.background.shape[:2]
self.pixel_height, self.pixel_width = shape
else:
background_color = Color(self.background_color)
background_rgb = np.array(background_color.get_rgb())
ones = np.ones(
(self.pixel_height, self.pixel_width, 1),
dtype = 'uint8'
)
else:
pixels = np.array(image_array).astype('uint8')
assert len(pixels.shape) == 3 and pixels.shape[2] == 3
return pixels
self.background = np.dot(
ones, background_rgb.reshape((1, 3))
)
def paint_region(region, image_array = None, color = None):
pixels = get_pixels(image_array)
assert region.shape == pixels.shape[:2]
if color is None:
#Random dark color
rgb = 0.5 * np.random.random(3)
else:
rgb = np.array(Color(color).get_rgb())
pixels[region.bool_grid] = (255*rgb).astype('uint8')
return pixels
def get_image(self):
return self.pixel_array
def paint_mobject(mobject, image_array = None):
return paint_mobjects([mobject], image_array)
def reset(self):
self.pixel_array = np.array(self.background)
def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True):
pixels = get_pixels(image_array)
height = pixels.shape[0]
width = pixels.shape[1]
space_height = SPACE_HEIGHT
space_width = SPACE_HEIGHT * width / height
pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8')
# def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE
# if image_array is None:
# return np.zeros(
# (DEFAULT_HEIGHT, DEFAULT_WIDTH, 3),
# dtype = 'uint8'
# )
# else:
# pixels = np.array(image_array).astype('uint8')
# assert len(pixels.shape) == 3 and pixels.shape[2] == 3
# return pixels
# def paint_region(region, image_array = None, color = None):
# pixels = get_pixels(image_array)
# assert region.shape == pixels.shape[:2]
# if color is None:
# #Random dark color
# rgb = 0.5 * np.random.random(3)
# else:
# rgb = np.array(Color(color).get_rgb())
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
# return pixels
def capture_mobject(self, mobject):
return self.capture_mobjects([mobject])
def capture_mobjects(self, mobjects, include_sub_mobjects = True):
# pixels = get_pixels(image_array)
# height = pixels.shape[0]
# width = pixels.shape[1]
# space_height = SPACE_HEIGHT
# space_width = SPACE_HEIGHT * width / height
# pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8')
if include_sub_mobjects:
all_families = [
@ -53,122 +88,82 @@ def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True):
mobjects = reduce(op.add, all_families, [])
for mobject in mobjects:
if mobject.get_num_points() == 0:
continue
#bunch these together so rgbs never get lost from points
points_and_rgbs = np.append(
mobject.points,
255*mobject.rgbs,
axis = 1
self.display_points(
mobject.points, mobject.rgbs,
mobject.point_thickness
)
points_and_rgbs = place_on_screen(
points_and_rgbs,
space_width, space_height
)
#Map points to pixel space, which requires rescaling and shifting
#Remember, 2*space_height -> height
points_and_rgbs[:,0] = points_and_rgbs[:,0]*width/space_width/2 + width/2
def display_points(self, points, rgbs, thickness):
points = self.project_onto_screen(points)
pixel_coordinates = self.pixel_coordinates_of_points(points)
rgbs = (255*rgbs).astype('uint8')
on_screen_indices = self.on_screen_pixels(pixel_coordinates)
pixel_coordinates = pixel_coordinates[on_screen_indices]
rgbs = rgbs[on_screen_indices]
flattener = np.array([1, self.width], dtype = 'int')
flattener = flattener.reshape((2, 1))
indices = np.dot(pixel_coordinates, flattener)[:,0]
pw, ph = self.pixel_width, self.pixel_height
self.pixel_array.reshape((pw*ph, 3))
self.pixel_array[indices] = rgbs.astype('uint8')
self.pixel_array.reshape((ph, pw, 3))
def project_onto_screen(points):
## TODO
points[:,2] = 0
def pixel_coordinates_of_points(self, points):
result = np.zeros((len(points), 2))
width_mult = self.pixel_width/self.space_width/2
width_add = self.pixel_width/2
height_mult = self.pixel_height/self.space_height/2
height_add = self.pixel_height/2
#Flip on y-axis as you go
points_and_rgbs[:,1] = -1*points_and_rgbs[:,1]*height/space_height/2 + height/2
points_and_rgbs = add_thickness(
points_and_rgbs.astype('int'),
mobject.point_thickness,
width, height
)
height_mult *= -1
points, rgbs = points_and_rgbs[:,:2], points_and_rgbs[:,2:]
flattener = np.array([[1], [width]], dtype = 'int')
indices = np.dot(points, flattener)[:,0]
pixels[indices] = rgbs.astype('uint8')
return pixels.reshape((height, width, 3))
result[:,0] = points[:,0]*width_mult + width_add
result[:,1] = points[:,1]*height_mult + height_add
return result
def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
"""
Imagine dragging each pixel around like a paintbrush in
a plus-sign-shaped pixel arrangement surrounding it.
def on_screen_pixels(self, pixel_coordinates):
return (pixel_coordinates[:,0] < 0) | \
(pixel_coordinates[:,0] >= width) | \
(pixel_coordinates[:,1] < 0) | \
(pixel_coordinates[:,1] >= height)
Pass rgb = None to do nothing to them
"""
thickness = adjusted_thickness(thickness, width, height)
original = np.array(pixel_indices_and_rgbs)
n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
for nudge in range(-thickness/2+1, thickness/2+1):
if nudge == 0:
continue
for x, y in [[nudge, 0], [0, nudge]]:
pixel_indices_and_rgbs = np.append(
pixel_indices_and_rgbs,
original+([x, y] + [0]*n_extra_columns),
axis = 0
)
admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
(pixel_indices_and_rgbs[:,0] < width) & \
(pixel_indices_and_rgbs[:,1] >= 0) & \
(pixel_indices_and_rgbs[:,1] < height)
return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(thickness, width, height):
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
# """
# Imagine dragging each pixel around like a paintbrush in
# a plus-sign-shaped pixel arrangement surrounding it.
# Pass rgb = None to do nothing to them
# """
# thickness = adjusted_thickness(thickness, width, height)
# original = np.array(pixel_indices_and_rgbs)
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
# for nudge in range(-thickness/2+1, thickness/2+1):
# if nudge == 0:
# continue
# for x, y in [[nudge, 0], [0, nudge]]:
# pixel_indices_and_rgbs = np.append(
# pixel_indices_and_rgbs,
# original+([x, y] + [0]*n_extra_columns),
# axis = 0
# )
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
# (pixel_indices_and_rgbs[:,0] < width) & \
# (pixel_indices_and_rgbs[:,1] >= 0) & \
# (pixel_indices_and_rgbs[:,1] < height)
# return pixel_indices_and_rgbs[admissibles]
def adjusted_thickness(thickness, width, height):
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
factor = (big_width + big_height) / (width + height)
return 1 + (thickness-1)/factor
def place_on_screen(points_and_rgbs, space_width, space_height):
"""
Projects points to 2d space and remove those outside a
the space constraints.
Pass rbgs = None to do nothing to them.
"""
# Remove 3rd column
points_and_rgbs = np.append(
points_and_rgbs[:, :2],
points_and_rgbs[:, 3:],
axis = 1
)
#Removes points out of space
to_keep = (abs(points_and_rgbs[:,0]) < space_width) & \
(abs(points_and_rgbs[:,1]) < space_height)
return points_and_rgbs[to_keep]
def get_file_path(name, extension):
file_path = os.path.join(MOVIE_DIR, name)
if not file_path.endswith(extension):
file_path += extension
directory = os.path.split(file_path)[0]
if not os.path.exists(directory):
os.makedirs(directory)
return file_path
def write_to_movie(scene, name):
file_path = get_file_path(name, ".mp4")
print "Writing to %s"%file_path
fps = int(1/scene.frame_duration)
dim = (scene.width, scene.height)
command = [
FFMPEG_BIN,
'-y', # overwrite output file if it exists
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-s', '%dx%d'%dim, # size of one frame
'-pix_fmt', 'rgb24',
'-r', str(fps), # frames per second
'-i', '-', # The imput comes from a pipe
'-an', # Tells FFMPEG not to expect any audio
'-vcodec', 'mpeg',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-loglevel', 'error',
file_path,
]
process = sp.Popen(command, stdin=sp.PIPE)
for frame in scene.frames:
process.stdin.write(frame.tostring())
process.stdin.close()
process.wait()
return 1 + (thickness-1)/facto

View File

@ -8,13 +8,13 @@ import os
import copy
from tqdm import tqdm as ProgressDisplay
import inspect
import subprocess as sp
from helpers import *
import displayer as disp
from camera import Camera
from tk_scene import TkSceneRoot
from mobject import Mobject
from animation.transform import ApplyMethod
class Scene(object):
DEFAULT_CONFIG = {
@ -27,16 +27,11 @@ class Scene(object):
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
if self.background is not None:
self.original_background = np.array(self.background)
#TODO, Error checking?
else:
self.original_background = np.zeros(
(self.height, self.width, 3),
dtype = 'uint8'
self.camera = Camera(
pixel_width = self.width,
pixel_height = self.height,
background = self.background
)
self.background = self.original_background
self.curr_frame = self.background
self.frames = []
self.mobjects = []
self.num_animations = 0
@ -57,11 +52,7 @@ class Scene(object):
return self
def get_frame(self):
return self.curr_frame
def set_frame(self, frame):
self.curr_frame = frame
return self
return self.camera.get_image()
def add(self, *mobjects):
"""
@ -70,9 +61,9 @@ class Scene(object):
"""
if not all_elements_are_instances(mobjects, Mobject):
raise Exception("Adding something which is not a mobject")
self.set_frame(disp.paint_mobjects(mobjects, self.get_frame()))
old_mobjects = filter(lambda m : m not in mobjects, self.mobjects)
self.mobjects = old_mobjects + list(mobjects)
self.camera.capture_mobjects(mobjects)
return self
def add_mobjects_among(self, values):
@ -101,56 +92,17 @@ class Scene(object):
def bring_to_back(self, mobject):
self.remove(mobject)
self.mobjects = [mobject] + self.mobjects
self.repaint_mojects()
return self
def clear(self):
self.reset_background()
self.mobjects = []
self.set_frame(self.background)
return self
def highlight_region(self, region, color = None):
self.background = disp.paint_region(
region,
image_array = self.background,
color = color,
)
self.repaint_mojects()
return self
def highlight_region_over_time_range(self, region, time_range = None, color = "black"):
if time_range:
frame_range = map(lambda t : t / self.frame_duration, time_range)
frame_range[0] = max(frame_range[0], 0)
frame_range[1] = min(frame_range[1], len(self.frames))
else:
frame_range = (0, len(self.frames))
for index in range(frame_range[0], frame_range[1]):
self.frames[index] = disp.paint_region(
region,
image_array = self.frames[index],
color = color
)
def reset_background(self):
self.background = self.original_background
return self
def repaint_mojects(self):
self.set_frame(disp.paint_mobjects(self.mobjects, self.background))
self.camera.reset()
self.camera.capture_mobjects(self.mobjects)
return self
def paint_into_background(self, *mobjects):
#This way current mobjects don't have to be redrawn with
#every change, and one can later call "apply" without worrying
#about it applying to these mobjects
self.background = disp.paint_mobjects(mobjects, self.background)
return self
def set_frame_as_background(self):
self.background = self.get_frame()
def play(self, *animations, **kwargs):
self.num_animations += 1
if "run_time" in kwargs:
@ -170,11 +122,12 @@ class Scene(object):
lambda m : m not in moving_mobjects,
bundle.submobject_family()
)
background = disp.paint_mobjects(
self.camera.reset()
self.camera.capture_mobjects(
static_mobjects,
self.background,
include_sub_mobjects = False
)
static_image = self.camera.get_image()
times = np.arange(0, run_time, self.frame_duration)
time_progression = ProgressDisplay(times)
@ -186,15 +139,15 @@ class Scene(object):
for t in time_progression:
for animation in animations:
animation.update(t / animation.run_time)
new_frame = disp.paint_mobjects(
[anim.mobject for anim in animations],
background
)
self.frames.append(new_frame)
self.camera.capture_mobjects([
anim.mobject
for anim in animations
])
self.frames.append(self.camera.get_image())
self.camera.set_image(static_image)
for animation in animations:
animation.clean_up()
self.add(*[anim.mobject for anim in animations])
self.repaint_mojects()
return self
def play_over_time_range(self, t0, t1, *animations):
@ -215,14 +168,14 @@ class Scene(object):
for animation in animations:
animation.update((t-t0)/(t1 - t0))
index = int(t/self.frame_duration)
self.frames[index] = disp.paint_mobjects(moving_mobjects, self.frames[index])
self.camera.set_image(self.frames[index])
self.camera.capture_mobjects(moving_mobjects)
self.frames[index] = self.camera.get_image()
for animation in animations:
animation.clean_up()
self.repaint_mojects()
return self
def apply(self, mobject_method, *args, **kwargs):
self.play(ApplyMethod(mobject_method, *args, **kwargs))
def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
return self
@ -243,14 +196,6 @@ class Scene(object):
]
return self
def write_to_movie(self, name = None):
if len(self.frames) == 0:
print "No frames, I'll just save an image instead"
self.show_frame()
self.save_image(name = name)
return
disp.write_to_movie(self, name or str(self))
def show_frame(self):
Image.fromarray(self.get_frame()).show()
@ -265,6 +210,52 @@ class Scene(object):
os.makedirs(path)
Image.fromarray(self.get_frame()).save(full_path)
def get_movie_file_path(self, name, extension):
file_path = os.path.join(MOVIE_DIR, name)
if not file_path.endswith(extension):
file_path += extension
directory = os.path.split(file_path)[0]
if not os.path.exists(directory):
os.makedirs(directory)
return file_path
def write_to_movie(self, name = None):
if len(self.frames) == 0:
print "No frames, I'll just save an image instead"
self.show_frame()
self.save_image(name = name)
return
if name is None:
name = str(self)
file_path = self.get_movie_file_path(name, ".mp4")
print "Writing to %s"%file_path
fps = int(1/self.frame_duration)
dim = (self.width, self.height)
command = [
FFMPEG_BIN,
'-y', # overwrite output file if it exists
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-s', '%dx%d'%dim, # size of one frame
'-pix_fmt', 'rgb24',
'-r', str(fps), # frames per second
'-i', '-', # The imput comes from a pipe
'-an', # Tells FFMPEG not to expect any audio
'-vcodec', 'mpeg',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-loglevel', 'error',
file_path,
]
process = sp.Popen(command, stdin=sp.PIPE)
for frame in self.frames:
process.stdin.write(frame.tostring())
process.stdin.close()
process.wait()
# To list possible args that subclasses have
# Elements should always be a tuple
args_list = []

View File

@ -27,8 +27,6 @@ class PiCreature(Mobject):
'mouth',
]
WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
MOUTH_NAMES = ["smile", "frown", "straight_mouth"]
def __init__(self, **kwargs):
Mobject.__init__(self, **kwargs)
for part_name in self.PART_NAMES:
@ -39,43 +37,26 @@ class PiCreature(Mobject):
if part_name not in self.WHITE_PART_NAMES:
mob.highlight(self.color)
setattr(self, part_name, mob)
self.add(mob)
self.eyes = Mobject(self.left_eye, self.right_eye)
self.legs = Mobject(self.left_leg, self.right_leg)
mouth_center = self.get_mouth_center()
self.mouth.center()
self.smile = self.mouth
self.frown = self.mouth.copy().rotate(np.pi, RIGHT)
self.straight_mouth = TexMobject("-").scale(0.7)
for mouth in self.smile, self.frown, self.straight_mouth:
mouth.sort_points(lambda p : p[0])
mouth.highlight(self.color) ##to blend into background
mouth.shift(mouth_center)
self.digest_mobject_attrs()
self.give_smile()
self.mouth.center().shift(self.get_mouth_center())
self.add(self.mouth)
self.scale(PI_CREATURE_SCALE_VAL)
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_PART_NAMES+self.MOUTH_NAMES
for pn in self.WHITE_PART_NAMES
]
def get_mouth_center(self):
result = self.body.get_center()
result[0] = self.eyes.get_center()[0]
return result
# 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):
for part in set(self.get_parts()).difference(self.get_white_parts()):
@ -86,25 +67,6 @@ class PiCreature(Mobject):
self.shift(destination-self.get_bottom())
return self
def change_mouth_to(self, mouth_name):
#TODO, This is poorly implemented
self.mouth = getattr(self, mouth_name)
self.sub_mobjects = list_update(
self.sub_mobjects,
self.get_parts()
)
self.mouth.highlight(WHITE)
return self
def give_smile(self):
return self.change_mouth_to("smile")
def give_frown(self):
return self.change_mouth_to("frown")
def give_straight_face(self):
return self.change_mouth_to("straight_mouth")
def get_eye_center(self):
return self.eyes.get_center()