3b1b-manim/mobject/simple_mobjects.py
2015-08-03 22:23:00 -07:00

224 lines
No EOL
6.9 KiB
Python

import numpy as np
import itertools as it
from mobject import Mobject, Mobject1D, Mobject2D, CompoundMobject
from constants import *
from helpers import *
class Point(Mobject):
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"
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),
*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)
self.length = length
self.normal = np.array(normal)
self.tip_length = tip_length
Mobject1D.__init__(self, *args, **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]
])
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):
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)
density *= max(self.get_length(), 0.1)
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)
])
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
class CurvedLine(Line):
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)
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
for t in np.arange(0, 1, self.epsilon)
])
class Circle(Mobject1D):
DEFAULT_COLOR = "red"
def generate_points(self):
self.add_points([
(np.cos(theta), np.sin(theta), 0)
for theta in np.arange(0, 2 * np.pi, self.epsilon)
])
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):
return Mobject.get_center(self)+self.center_offset
def move_tip_to(self, point):
self.shift(point - self.get_tip())
return self
def pin_to(self, mobject):
self.move_tip_to(sum([
mobject.get_center(),
-self.direction * mobject.get_width()/2,
UP * mobject.get_height()/2,
]))
return self
def add_content(self, mobject):
mobject.scale(0.75*self.get_width() / mobject.get_width())
mobject.shift(self.get_bubble_center())
self.content = CompoundMobject(self.content, mobject)
self.add(self.content)
return self
def write(self, text):
self.add_content(text_mobject(text))
return self
def clear(self):
num_content_points = self.content.points.shape[0]
self.points = self.points[:-num_content_points]
self.rgbs = self.rgbs[:-num_content_points]
self.contents = Mobject()
return self
class SpeechBubble(Bubble):
def __init__(self, *args, **kwargs):
#TODO
pass
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
)