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 *
|
|
|
|
|
|
|
|
class Point(Mobject):
|
2015-08-07 18:10:00 -07:00
|
|
|
DEFAULT_COLOR = "black"
|
2015-06-10 22:00:35 -07:00
|
|
|
def __init__(self, point = (0, 0, 0), *args, **kwargs):
|
|
|
|
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"
|
2015-08-01 11:34:33 -07:00
|
|
|
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),
|
2015-08-07 18:10:00 -07:00
|
|
|
density = DEFAULT_POINT_DENSITY_1D,
|
2015-08-01 11:34:33 -07:00
|
|
|
*args, **kwargs):
|
2015-06-10 22:00:35 -07:00
|
|
|
self.point = np.array(point)
|
2015-06-22 10:14:53 -07:00
|
|
|
if tail is not None:
|
|
|
|
direction = self.point - tail
|
|
|
|
length = np.linalg.norm(direction)
|
|
|
|
self.direction = np.array(direction) / np.linalg.norm(direction)
|
2015-08-07 18:10:00 -07:00
|
|
|
density *= max(length, 0.1)
|
2015-06-22 10:14:53 -07:00
|
|
|
self.length = length
|
2015-06-10 22:00:35 -07:00
|
|
|
self.normal = np.array(normal)
|
|
|
|
self.tip_length = tip_length
|
2015-08-07 18:10:00 -07:00
|
|
|
Mobject1D.__init__(self, density = density, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
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]
|
|
|
|
])
|
|
|
|
|
2015-08-01 11:34:33 -07:00
|
|
|
def nudge(self, distance = None):
|
|
|
|
if distance is None:
|
|
|
|
distance = self.DEFAULT_NUDGE_DISTANCE
|
|
|
|
return self.shift(-self.direction * distance)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
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):
|
|
|
|
center = np.array(center)
|
|
|
|
if center.size == 1:
|
|
|
|
raise Exception("Center must have 2 or 3 coordinates!")
|
|
|
|
elif center.size == 2:
|
|
|
|
center = np.append(center, [0])
|
|
|
|
self.center_point = center
|
|
|
|
self.radius = radius
|
|
|
|
Mobject1D.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
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):
|
|
|
|
RADIUS = 0.3
|
|
|
|
DEFAULT_COLOR = "white"
|
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
|
|
|
(sgn * x, x, 0)
|
|
|
|
for x in np.arange(-self.RADIUS / 2, self.RADIUS/2, self.epsilon)
|
|
|
|
for sgn in [-1, 1]
|
|
|
|
])
|
|
|
|
|
|
|
|
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)
|
2015-08-03 22:23:00 -07:00
|
|
|
density *= max(self.get_length(), 0.1)
|
2015-06-10 22:00:35 -07:00
|
|
|
Mobject1D.__init__(self, density = density, *args, **kwargs)
|
|
|
|
|
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
|
|
|
t * self.end + (1 - t) * self.start
|
|
|
|
for t in np.arange(0, 1, self.epsilon)
|
|
|
|
])
|
|
|
|
|
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-12 14:24:36 -07:00
|
|
|
class NewArrow(Line):
|
|
|
|
DEFAULT_COLOR = "white"
|
|
|
|
DEFAULT_TIP_LENGTH = 0.25
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
if "tip_length" in kwargs:
|
|
|
|
tip_length = kwargs.pop("tip_length")
|
|
|
|
else:
|
|
|
|
tip_length = self.DEFAULT_TIP_LENGTH
|
|
|
|
Line.__init__(self, *args, **kwargs)
|
|
|
|
self.add_tip(tip_length)
|
|
|
|
|
|
|
|
def add_tip(self, tip_length):
|
|
|
|
pass
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class CurvedLine(Line):
|
2015-06-19 08:31:02 -07:00
|
|
|
def __init__(self, start, end, via = None, *args, **kwargs):
|
|
|
|
if via == None:
|
|
|
|
via = rotate_vector(
|
|
|
|
end - start,
|
|
|
|
np.pi/3, [0,0,1]
|
|
|
|
) + start
|
|
|
|
self.via = via
|
|
|
|
Line.__init__(self, start, end, *args, **kwargs)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
2015-06-19 08:31:02 -07:00
|
|
|
4*(0.25-t*(1-t))*(t*self.end + (1-t)*self.start) +
|
|
|
|
4*t*(1-t)*self.via
|
2015-06-10 22:00:35 -07:00
|
|
|
for t in np.arange(0, 1, self.epsilon)
|
|
|
|
])
|
|
|
|
|
|
|
|
class Circle(Mobject1D):
|
|
|
|
DEFAULT_COLOR = "red"
|
2015-08-07 18:10:00 -07:00
|
|
|
def __init__(self, radius = 1.0, **kwargs):
|
|
|
|
self.radius = radius
|
|
|
|
Mobject1D.__init__(self, **kwargs)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def generate_points(self):
|
|
|
|
self.add_points([
|
2015-08-07 18:10:00 -07:00
|
|
|
(self.radius*np.cos(theta), self.radius*np.sin(theta), 0)
|
|
|
|
for theta in np.arange(0, 2 * np.pi, self.epsilon/self.radius)
|
2015-06-10 22:00:35 -07:00
|
|
|
])
|
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class Rectangle(Mobject1D):
|
|
|
|
DEFAULT_COLOR = "yellow"
|
|
|
|
def __init__(self, height = 2.0, width = 2.0, **kwargs):
|
|
|
|
self.height, self.width = height, width
|
|
|
|
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):
|
|
|
|
def __init__(self, side_length = 2.0, **kwargs):
|
|
|
|
Rectangle.__init__(self, side_length, side_length, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-01 11:34:33 -07:00
|
|
|
class Bubble(Mobject):
|
|
|
|
def __init__(self, direction = LEFT, index_of_tip = -1, center = ORIGIN):
|
|
|
|
self.direction = direction
|
|
|
|
self.content = Mobject()
|
|
|
|
self.index_of_tip = index_of_tip
|
|
|
|
self.center_offset = center - Mobject.get_center(self)
|
|
|
|
if direction[0] > 0:
|
|
|
|
self.rotate(np.pi, UP)
|
|
|
|
|
|
|
|
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-08-12 14:24:36 -07:00
|
|
|
INITIAL_WIDTH = 6
|
|
|
|
INITIAL_HEIGHT = 4
|
2015-08-01 11:34:33 -07:00
|
|
|
def __init__(self, *args, **kwargs):
|
2015-08-12 14:24:36 -07:00
|
|
|
Mobject.__init__(self, *args, **kwargs)
|
|
|
|
complex_power = 0.9
|
|
|
|
radius = self.INITIAL_WIDTH/2
|
|
|
|
circle = Circle(density = radius*DEFAULT_POINT_DENSITY_1D)
|
|
|
|
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)
|
|
|
|
self.points[:,1] *= float(self.INITIAL_HEIGHT)/self.INITIAL_WIDTH
|
|
|
|
Bubble.__init__(self, direction = LEFT)
|
2015-08-01 11:34:33 -07:00
|
|
|
|
|
|
|
class ThoughtBubble(Bubble):
|
|
|
|
NUM_BULGES = 7
|
|
|
|
INITIAL_INNER_RADIUS = 1.8
|
|
|
|
INITIAL_WIDTH = 6
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
Mobject.__init__(self, *args, **kwargs)
|
|
|
|
self.add(Circle().scale(0.15).shift(2.5*DOWN+2*LEFT))
|
|
|
|
self.add(Circle().scale(0.3).shift(2*DOWN+1.5*LEFT))
|
|
|
|
for n in range(self.NUM_BULGES):
|
|
|
|
theta = 2*np.pi*n/self.NUM_BULGES
|
|
|
|
self.add(Circle().shift((np.cos(theta), np.sin(theta), 0)))
|
|
|
|
self.filter_out(lambda p : np.linalg.norm(p) < self.INITIAL_INNER_RADIUS)
|
|
|
|
self.stretch_to_fit_width(self.INITIAL_WIDTH)
|
|
|
|
self.highlight("white")
|
|
|
|
Bubble.__init__(
|
|
|
|
self,
|
|
|
|
index_of_tip = np.argmin(self.points[:,1]),
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|