2015-06-10 22:00:35 -07:00
|
|
|
import numpy as np
|
|
|
|
import itertools as it
|
|
|
|
|
|
|
|
from mobject import Mobject, Mobject1D, Mobject2D, CompoundMobject
|
2015-08-12 14:24:36 -07:00
|
|
|
from image_mobject import text_mobject
|
2015-06-10 22:00:35 -07:00
|
|
|
from constants import *
|
|
|
|
from helpers import *
|
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class Point(Mobject):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"color" : BLACK,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
|
|
|
def __init__(self, location = ORIGIN, **kwargs):
|
|
|
|
digest_config(self, Point, kwargs, locals())
|
|
|
|
Mobject.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def generate_points(self):
|
2015-09-30 14:22:17 -07:00
|
|
|
self.add_points([self.location])
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
|
|
|
class Dot(Mobject1D): #Use 1D density, even though 2D
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"radius" : 0.05
|
|
|
|
}
|
|
|
|
def __init__(self, center_point = ORIGIN, **kwargs):
|
|
|
|
digest_config(self, Dot, kwargs, locals())
|
|
|
|
Mobject1D.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
|
|
|
np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center_point
|
|
|
|
for t in np.arange(self.epsilon, self.radius, self.epsilon)
|
|
|
|
for new_epsilon in [2*np.pi*self.epsilon*self.radius/t]
|
|
|
|
for theta in np.arange(0, 2 * np.pi, new_epsilon)
|
|
|
|
])
|
|
|
|
|
|
|
|
class Cross(Mobject1D):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"color" : YELLOW,
|
2015-09-28 16:25:18 -07:00
|
|
|
"radius" : 0.3
|
|
|
|
}
|
|
|
|
def __init__(self, center_point = ORIGIN, **kwargs):
|
|
|
|
digest_config(self, Cross, kwargs, locals())
|
|
|
|
Mobject1D.__init__(self, **kwargs)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
|
|
|
(sgn * x, x, 0)
|
2015-09-28 16:25:18 -07:00
|
|
|
for x in np.arange(-self.radius / 2, self.radius/2, self.epsilon)
|
2015-06-10 22:00:35 -07:00
|
|
|
for sgn in [-1, 1]
|
|
|
|
])
|
2015-09-28 16:25:18 -07:00
|
|
|
self.shift(self.center_point)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
class Line(Mobject1D):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"min_density" : 0.1
|
|
|
|
}
|
|
|
|
def __init__(self, start, end, **kwargs):
|
|
|
|
digest_config(self, Line, kwargs)
|
2015-08-17 11:12:56 -07:00
|
|
|
self.set_start_and_end(start, end)
|
2015-09-28 16:25:18 -07:00
|
|
|
Mobject1D.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
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])
|
|
|
|
]
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
2015-10-08 11:50:49 -07:00
|
|
|
self.add_line(self.start, self.end, self.min_density)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-06-13 19:00:23 -07:00
|
|
|
def get_length(self):
|
|
|
|
return np.linalg.norm(self.start - self.end)
|
|
|
|
|
|
|
|
def get_slope(self):
|
|
|
|
rise, run = [
|
|
|
|
float(self.end[i] - self.start[i])
|
|
|
|
for i in [1, 0]
|
|
|
|
]
|
|
|
|
return rise/run
|
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
class Arrow(Line):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"color" : WHITE,
|
2015-09-28 16:25:18 -07:00
|
|
|
"tip_length" : 0.25
|
|
|
|
}
|
2015-08-12 14:24:36 -07:00
|
|
|
def __init__(self, *args, **kwargs):
|
2015-09-28 16:25:18 -07:00
|
|
|
digest_config(self, Arrow, kwargs)
|
2015-08-12 14:24:36 -07:00
|
|
|
Line.__init__(self, *args, **kwargs)
|
2015-09-28 16:25:18 -07:00
|
|
|
self.add_tip()
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def add_tip(self):
|
2015-10-06 18:41:53 -07:00
|
|
|
num_points = self.get_num_points()
|
2015-08-17 11:12:56 -07:00
|
|
|
vect = self.start-self.end
|
2015-09-28 16:25:18 -07:00
|
|
|
vect = vect*self.tip_length/np.linalg.norm(vect)
|
2015-08-17 11:12:56 -07:00
|
|
|
self.add_points([
|
|
|
|
interpolate(self.end, self.end+v, t)
|
2015-09-28 16:25:18 -07:00
|
|
|
for t in np.arange(0, 1, self.tip_length*self.epsilon)
|
2015-08-17 11:12:56 -07:00
|
|
|
for v in [
|
|
|
|
rotate_vector(vect, np.pi/4, axis)
|
|
|
|
for axis in IN, OUT
|
|
|
|
]
|
|
|
|
])
|
2015-10-06 18:41:53 -07:00
|
|
|
self.num_tip_points = self.get_num_points()-num_points
|
|
|
|
|
|
|
|
def remove_tip(self):
|
|
|
|
if not hasattr(self, "num_tip_points"):
|
|
|
|
return self
|
|
|
|
for attr in "points", "rgbs":
|
|
|
|
setattr(self, attr, getattr(self, attr)[:-self.num_tip_points])
|
|
|
|
return self
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class CurvedLine(Line):
|
2015-09-28 16:25:18 -07:00
|
|
|
def __init__(self, start, end, via = None, **kwargs):
|
2015-08-17 11:12:56 -07:00
|
|
|
self.set_start_and_end(start, end)
|
2015-06-19 08:31:02 -07:00
|
|
|
if via == None:
|
2015-08-17 11:12:56 -07:00
|
|
|
self.via = rotate_vector(
|
|
|
|
self.end - self.start,
|
2015-06-19 08:31:02 -07:00
|
|
|
np.pi/3, [0,0,1]
|
2015-08-17 11:12:56 -07:00
|
|
|
) + self.start
|
|
|
|
elif isinstance(via, Mobject):
|
|
|
|
self.via = via.get_center()
|
|
|
|
else:
|
|
|
|
self.via = via
|
2015-09-28 16:25:18 -07:00
|
|
|
Line.__init__(self, start, end, **kwargs)
|
2015-06-19 08:31:02 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
2015-08-17 11:12:56 -07:00
|
|
|
interpolate(
|
|
|
|
interpolate(self.start, self.end, t),
|
|
|
|
self.via,
|
|
|
|
t*(1-t)
|
|
|
|
)
|
2015-06-10 22:00:35 -07:00
|
|
|
for t in np.arange(0, 1, self.epsilon)
|
|
|
|
])
|
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
|
|
|
|
|
|
|
|
class PartialCircle(Mobject1D):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"radius" : 1.0,
|
|
|
|
"start_angle" : 0
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2015-10-20 21:55:46 -07:00
|
|
|
def __init__(self, angle, **kwargs):
|
|
|
|
digest_config(self, PartialCircle, kwargs, locals())
|
2015-08-07 18:10:00 -07:00
|
|
|
Mobject1D.__init__(self, **kwargs)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
2015-10-20 21:55:46 -07:00
|
|
|
sign = 1 if self.angle >= 0 else -1
|
2015-06-10 22:00:35 -07:00
|
|
|
self.add_points([
|
2015-08-07 18:10:00 -07:00
|
|
|
(self.radius*np.cos(theta), self.radius*np.sin(theta), 0)
|
2015-10-20 21:55:46 -07:00
|
|
|
for theta in np.arange(
|
|
|
|
self.start_angle,
|
|
|
|
self.start_angle+self.angle,
|
|
|
|
sign*self.epsilon/self.radius
|
|
|
|
)
|
2015-06-10 22:00:35 -07:00
|
|
|
])
|
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
class Circle(PartialCircle):
|
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"color" : RED,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, Circle, kwargs)
|
|
|
|
PartialCircle.__init__(self, angle = 2*np.pi, **kwargs)
|
|
|
|
|
2015-10-08 11:50:49 -07:00
|
|
|
class Polygon(Mobject1D):
|
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"color" : GREEN_D,
|
2015-10-09 19:53:38 -07:00
|
|
|
"edge_colors" : None
|
2015-10-08 11:50:49 -07:00
|
|
|
}
|
|
|
|
def __init__(self, *points, **kwargs):
|
|
|
|
assert len(points) > 1
|
|
|
|
digest_config(self, Polygon, kwargs)
|
2015-10-09 19:53:38 -07:00
|
|
|
self.original_points = points
|
2015-10-08 11:50:49 -07:00
|
|
|
Mobject1D.__init__(self, **kwargs)
|
|
|
|
|
|
|
|
def generate_points(self):
|
2015-10-09 19:53:38 -07:00
|
|
|
if self.edge_colors:
|
|
|
|
colors = it.cycle(self.edge_colors)
|
|
|
|
else:
|
|
|
|
colors = it.cycle([self.color])
|
|
|
|
self.indices_of_vertices = []
|
|
|
|
points = list(self.original_points)
|
|
|
|
points.append(points[0])
|
2015-10-08 11:50:49 -07:00
|
|
|
for start, end in zip(points, points[1:]):
|
2015-10-09 19:53:38 -07:00
|
|
|
self.indices_of_vertices.append(self.get_num_points())
|
|
|
|
self.add_line(start, end, color = colors.next())
|
|
|
|
|
|
|
|
|
|
|
|
def get_vertices(self):
|
|
|
|
return self.points[self.indices_of_vertices]
|
2015-10-08 11:50:49 -07:00
|
|
|
|
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class Rectangle(Mobject1D):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-20 21:55:46 -07:00
|
|
|
"color" : YELLOW,
|
2015-09-28 16:25:18 -07:00
|
|
|
"height" : 2.0,
|
|
|
|
"width" : 4.0
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, Rectangle, kwargs)
|
2015-08-07 18:10:00 -07:00
|
|
|
Mobject1D.__init__(self, **kwargs)
|
|
|
|
|
|
|
|
def generate_points(self):
|
|
|
|
wh = [self.width/2.0, self.height/2.0]
|
|
|
|
self.add_points([
|
|
|
|
(x, u, 0) if dim==0 else (u, x, 0)
|
|
|
|
for dim in 0, 1
|
|
|
|
for u in wh[1-dim], -wh[1-dim]
|
|
|
|
for x in np.arange(-wh[dim], wh[dim], self.epsilon)
|
|
|
|
])
|
|
|
|
|
|
|
|
class Square(Rectangle):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-09 19:53:38 -07:00
|
|
|
"side_length" : 2.0,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, Square, kwargs)
|
2015-10-09 19:53:38 -07:00
|
|
|
for arg in ["height", "width"]:
|
|
|
|
kwargs[arg] = self.side_length
|
2015-09-28 16:25:18 -07:00
|
|
|
Rectangle.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-01 11:34:33 -07:00
|
|
|
class Bubble(Mobject):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"direction" : LEFT,
|
|
|
|
"index_of_tip" : -1,
|
|
|
|
"center_point" : ORIGIN,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, Bubble, kwargs)
|
|
|
|
Mobject.__init__(self, **kwargs)
|
|
|
|
self.center_offset = self.center_point - Mobject.get_center(self)
|
|
|
|
if self.direction[0] > 0:
|
2015-08-01 11:34:33 -07:00
|
|
|
self.rotate(np.pi, UP)
|
2015-09-28 16:25:18 -07:00
|
|
|
self.content = Mobject()
|
2015-08-01 11:34:33 -07:00
|
|
|
|
|
|
|
def get_tip(self):
|
|
|
|
return self.points[self.index_of_tip]
|
|
|
|
|
|
|
|
def get_bubble_center(self):
|
2015-08-07 18:10:00 -07:00
|
|
|
return self.get_center()+self.center_offset
|
2015-08-01 11:34:33 -07:00
|
|
|
|
|
|
|
def move_tip_to(self, point):
|
|
|
|
self.shift(point - self.get_tip())
|
|
|
|
return self
|
|
|
|
|
2015-08-12 14:24:36 -07:00
|
|
|
def flip(self):
|
|
|
|
self.direction = -np.array(self.direction)
|
|
|
|
self.rotate(np.pi, UP)
|
|
|
|
return self
|
|
|
|
|
2015-08-01 11:34:33 -07:00
|
|
|
def pin_to(self, mobject):
|
2015-08-12 14:24:36 -07:00
|
|
|
mob_center = mobject.get_center()
|
|
|
|
if (mob_center[0] > 0) != (self.direction[0] > 0):
|
|
|
|
self.flip()
|
|
|
|
boundary_point = mobject.get_boundary_point(UP-self.direction)
|
|
|
|
vector_from_center = 1.5*(boundary_point-mob_center)
|
|
|
|
self.move_tip_to(mob_center+vector_from_center)
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def add_content(self, mobject):
|
2015-08-12 14:24:36 -07:00
|
|
|
scaled_width = 0.75*self.get_width()
|
|
|
|
if mobject.get_width() > scaled_width:
|
|
|
|
mobject.scale(scaled_width / mobject.get_width())
|
2015-08-01 11:34:33 -07:00
|
|
|
mobject.shift(self.get_bubble_center())
|
2015-08-07 18:10:00 -07:00
|
|
|
self.content = mobject
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def write(self, text):
|
|
|
|
self.add_content(text_mobject(text))
|
|
|
|
return self
|
|
|
|
|
|
|
|
def clear(self):
|
2015-08-07 18:10:00 -07:00
|
|
|
self.content = Mobject()
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
class SpeechBubble(Bubble):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"initial_width" : 4,
|
|
|
|
"initial_height" : 2,
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, SpeechBubble, kwargs)
|
|
|
|
Bubble.__init__(self, **kwargs)
|
|
|
|
|
|
|
|
def generate_points(self):
|
2015-08-12 14:24:36 -07:00
|
|
|
complex_power = 0.9
|
2015-09-28 16:25:18 -07:00
|
|
|
radius = self.initial_width/2
|
|
|
|
circle = Circle(radius = radius)
|
|
|
|
circle.scale(1.0/radius)
|
2015-08-12 14:24:36 -07:00
|
|
|
circle.apply_complex_function(lambda z : z**complex_power)
|
|
|
|
circle.scale(radius)
|
|
|
|
boundary_point_as_complex = radius*complex(-1)**complex_power
|
|
|
|
boundary_points = [
|
|
|
|
[
|
|
|
|
boundary_point_as_complex.real,
|
|
|
|
unit*boundary_point_as_complex.imag,
|
|
|
|
0
|
|
|
|
]
|
|
|
|
for unit in -1, 1
|
|
|
|
]
|
|
|
|
tip = radius*(1.5*LEFT+UP)
|
|
|
|
self.add(
|
|
|
|
circle,
|
|
|
|
Line(boundary_points[0], tip),
|
|
|
|
Line(boundary_points[1], tip)
|
|
|
|
)
|
|
|
|
self.highlight("white")
|
|
|
|
self.rotate(np.pi/2)
|
2015-09-28 16:25:18 -07:00
|
|
|
self.points[:,1] *= float(self.initial_height)/self.initial_width
|
2015-08-01 11:34:33 -07:00
|
|
|
|
|
|
|
class ThoughtBubble(Bubble):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
|
|
|
"num_bulges" : 7,
|
|
|
|
"initial_inner_radius" : 1.8,
|
|
|
|
"initial_width" : 6
|
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, ThoughtBubble, kwargs)
|
|
|
|
Bubble.__init__(self, **kwargs)
|
|
|
|
self.index_of_tip = np.argmin(self.points[:,1])
|
|
|
|
|
|
|
|
def generate_points(self):
|
2015-08-01 11:34:33 -07:00
|
|
|
self.add(Circle().scale(0.15).shift(2.5*DOWN+2*LEFT))
|
|
|
|
self.add(Circle().scale(0.3).shift(2*DOWN+1.5*LEFT))
|
2015-09-28 16:25:18 -07:00
|
|
|
for n in range(self.num_bulges):
|
|
|
|
theta = 2*np.pi*n/self.num_bulges
|
2015-08-01 11:34:33 -07:00
|
|
|
self.add(Circle().shift((np.cos(theta), np.sin(theta), 0)))
|
2015-09-28 16:25:18 -07:00
|
|
|
self.filter_out(lambda p : np.linalg.norm(p) < self.initial_inner_radius)
|
|
|
|
self.stretch_to_fit_width(self.initial_width)
|
2015-08-01 11:34:33 -07:00
|
|
|
self.highlight("white")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|