mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 22:13:30 +08:00
Chopping stuff up in Moser
This commit is contained in:
@ -34,7 +34,8 @@ for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR, TMP_IMAGE_DIR, TEX_IMAGE_
|
|||||||
PDF_DENSITY = 800
|
PDF_DENSITY = 800
|
||||||
SIZE_TO_REPLACE = "SizeHere"
|
SIZE_TO_REPLACE = "SizeHere"
|
||||||
TEX_TEXT_TO_REPLACE = "YourTextHere"
|
TEX_TEXT_TO_REPLACE = "YourTextHere"
|
||||||
TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex")
|
TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex")
|
||||||
|
TEMPLATE_TEXT_FILE = os.path.join(TEX_DIR, "text_template.tex")
|
||||||
|
|
||||||
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
|
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
|
||||||
|
|
||||||
|
@ -72,6 +72,9 @@ def there_and_back(t, inflection = 10.0):
|
|||||||
def not_quite_there(t, proportion = 0.7):
|
def not_quite_there(t, proportion = 0.7):
|
||||||
return proportion*high_inflection_0_to_1(t)
|
return proportion*high_inflection_0_to_1(t)
|
||||||
|
|
||||||
|
def wiggle(t, wiggles = 2):
|
||||||
|
return there_and_back(t) * np.sin(wiggles*np.pi*t)
|
||||||
|
|
||||||
### Functional Functions ###
|
### Functional Functions ###
|
||||||
|
|
||||||
def composition(func_list):
|
def composition(func_list):
|
||||||
|
@ -86,11 +86,16 @@ class VideoIcon(ImageMobject):
|
|||||||
ImageMobject.__init__(self, "video_icon", *args, **kwargs)
|
ImageMobject.__init__(self, "video_icon", *args, **kwargs)
|
||||||
self.scale(0.3)
|
self.scale(0.3)
|
||||||
|
|
||||||
|
def text_mobject(text, size = r"\Large"):
|
||||||
|
image = tex_to_image(text, size, TEMPLATE_TEXT_FILE)
|
||||||
|
assert(not isinstance(image, list))
|
||||||
|
return ImageMobject(image)
|
||||||
|
|
||||||
#Purely redundant function to make singulars and plurals sensible
|
#Purely redundant function to make singulars and plurals sensible
|
||||||
def tex_mobject(expression, size = "\HUGE"):
|
def tex_mobject(expression, size = r"\HUGE"):
|
||||||
return tex_mobjects(expression, size)
|
return tex_mobjects(expression, size)
|
||||||
|
|
||||||
def tex_mobjects(expression, size = "\HUGE"):
|
def tex_mobjects(expression, size = r"\HUGE"):
|
||||||
images = tex_to_image(expression, size)
|
images = tex_to_image(expression, size)
|
||||||
if isinstance(images, list):
|
if isinstance(images, list):
|
||||||
#TODO, is checking listiness really the best here?
|
#TODO, is checking listiness really the best here?
|
||||||
|
511
mobject.py
Normal file
511
mobject.py
Normal file
@ -0,0 +1,511 @@
|
|||||||
|
import numpy as np
|
||||||
|
import itertools as it
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
from random import random
|
||||||
|
from copy import deepcopy
|
||||||
|
from colour import Color
|
||||||
|
|
||||||
|
from constants import *
|
||||||
|
from helpers import *
|
||||||
|
import displayer as disp
|
||||||
|
|
||||||
|
|
||||||
|
class Mobject(object):
|
||||||
|
"""
|
||||||
|
Mathematical 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
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
color = None,
|
||||||
|
name = None,
|
||||||
|
center = None,
|
||||||
|
):
|
||||||
|
self.color = Color(color) if color else Color(self.DEFAULT_COLOR)
|
||||||
|
if not hasattr(self, "name"):
|
||||||
|
self.name = name or self.__class__.__name__
|
||||||
|
self.has_normals = hasattr(self, 'unit_normal')
|
||||||
|
self.points = np.zeros((0, 3))
|
||||||
|
self.rgbs = np.zeros((0, 3))
|
||||||
|
if self.has_normals:
|
||||||
|
self.unit_normals = np.zeros((0, 3))
|
||||||
|
self.generate_points()
|
||||||
|
if center:
|
||||||
|
self.center().shift(center)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
Image.fromarray(disp.paint_mobject(self)).show()
|
||||||
|
|
||||||
|
def save_image(self, name = None):
|
||||||
|
Image.fromarray(disp.paint_mobject(self)).save(
|
||||||
|
os.path.join(MOVIE_DIR, (name or str(self)) + ".png")
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_points(self, points, rgbs = None, color = None):
|
||||||
|
"""
|
||||||
|
points must be a Nx3 numpy array, as must rgbs if it is not None
|
||||||
|
"""
|
||||||
|
points = np.array(points)
|
||||||
|
num_new_points = points.shape[0]
|
||||||
|
self.points = np.append(self.points, points)
|
||||||
|
self.points = self.points.reshape((self.points.size / 3, 3))
|
||||||
|
if rgbs is None:
|
||||||
|
color = Color(color) if color else self.color
|
||||||
|
rgbs = np.array([color.get_rgb()] * num_new_points)
|
||||||
|
else:
|
||||||
|
if rgbs.shape != points.shape:
|
||||||
|
raise Exception("points and rgbs must have same shape")
|
||||||
|
self.rgbs = np.append(self.rgbs, rgbs).reshape(self.points.shape)
|
||||||
|
if self.has_normals:
|
||||||
|
self.unit_normals = np.append(
|
||||||
|
self.unit_normals,
|
||||||
|
np.array([self.unit_normal(point) for point in points])
|
||||||
|
).reshape(self.points.shape)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def rotate(self, angle, axis = [0, 0, 1]):
|
||||||
|
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]):
|
||||||
|
center = self.get_center()
|
||||||
|
self.shift(-center)
|
||||||
|
self.rotate(angle, axis)
|
||||||
|
self.shift(center)
|
||||||
|
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
|
||||||
|
return self
|
||||||
|
|
||||||
|
def center(self):
|
||||||
|
self.shift(-self.get_center())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scale(self, scale_factor):
|
||||||
|
self.points *= scale_factor
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scale_in_place(self, scale_factor):
|
||||||
|
center = self.get_center()
|
||||||
|
return self.center().scale(scale_factor).shift(center)
|
||||||
|
|
||||||
|
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 get_num_points(self):
|
||||||
|
return self.points.shape[0]
|
||||||
|
|
||||||
|
def pose_at_angle(self):
|
||||||
|
self.rotate(np.pi / 7)
|
||||||
|
self.rotate(np.pi / 7, [1, 0, 0])
|
||||||
|
return self
|
||||||
|
|
||||||
|
def apply_function(self, function):
|
||||||
|
self.points = np.apply_along_axis(function, 1, self.points)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def apply_complex_function(self, function):
|
||||||
|
def point_map((x, y, z)):
|
||||||
|
result = function(complex(x, y))
|
||||||
|
return (result.real, result.imag, 0)
|
||||||
|
return self.apply_function(point_map)
|
||||||
|
|
||||||
|
def highlight(self, color = "red", condition = lambda x : True):
|
||||||
|
"""
|
||||||
|
Condition is function which takes in one arguments, (x, y, z).
|
||||||
|
"""
|
||||||
|
#TODO, Should self.color change?
|
||||||
|
to_change = np.apply_along_axis(condition, 1, self.points)
|
||||||
|
self.rgbs[to_change, :] *= 0
|
||||||
|
self.rgbs[to_change, :] += Color(color).get_rgb()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fade(self, amount = 0.5):
|
||||||
|
self.rgbs *= amount
|
||||||
|
return self
|
||||||
|
|
||||||
|
def filter_out(self, condition):
|
||||||
|
to_eliminate = ~np.apply_along_axis(condition, 1, self.points)
|
||||||
|
self.points = self.points[to_eliminate]
|
||||||
|
self.rgbs = self.rgbs[to_eliminate]
|
||||||
|
return self
|
||||||
|
|
||||||
|
### Getters ###
|
||||||
|
def get_center(self):
|
||||||
|
return np.apply_along_axis(np.mean, 0, self.points)
|
||||||
|
|
||||||
|
def get_width(self):
|
||||||
|
return np.max(self.points[:, 0]) - np.min(self.points[:, 0])
|
||||||
|
|
||||||
|
def get_height(self):
|
||||||
|
return np.max(self.points[:, 1]) - np.min(self.points[:, 1])
|
||||||
|
|
||||||
|
### Stuff subclasses should deal with ###
|
||||||
|
def should_buffer_points(self):
|
||||||
|
# potentially changed in subclasses
|
||||||
|
return GENERALLY_BUFF_POINTS
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
#Typically implemented in subclass, unless purposefully left blank
|
||||||
|
pass
|
||||||
|
|
||||||
|
### Static Methods ###
|
||||||
|
def align_data(mobject1, mobject2):
|
||||||
|
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
|
||||||
|
if count1 == 0:
|
||||||
|
mobject1.add_points([(0, 0, 0)])
|
||||||
|
if count2 == 0:
|
||||||
|
mobject2.add_points([(0, 0, 0)])
|
||||||
|
if count1 == count2:
|
||||||
|
return
|
||||||
|
for attr in ['points', 'rgbs']:
|
||||||
|
new_arrays = make_even(getattr(mobject1, attr), getattr(mobject2, attr))
|
||||||
|
for array, mobject in zip(new_arrays, [mobject1, mobject2]):
|
||||||
|
setattr(mobject, attr, np.array(array))
|
||||||
|
|
||||||
|
def interpolate(mobject1, mobject2, target_mobject, alpha):
|
||||||
|
"""
|
||||||
|
Turns target_mobject into an interpolation between mobject1
|
||||||
|
and mobject2.
|
||||||
|
"""
|
||||||
|
Mobject.align_data(mobject1, mobject2)
|
||||||
|
for attr in ['points', 'rgbs']:
|
||||||
|
new_array = (1 - alpha) * getattr(mobject1, attr) + \
|
||||||
|
alpha * getattr(mobject2, attr)
|
||||||
|
setattr(target_mobject, attr, new_array)
|
||||||
|
|
||||||
|
class Mobject1D(Mobject):
|
||||||
|
def __init__(self, density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs):
|
||||||
|
self.epsilon = 1.0 / density
|
||||||
|
Mobject.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
class Mobject2D(Mobject):
|
||||||
|
def __init__(self, density = DEFAULT_POINT_DENSITY_2D, *args, **kwargs):
|
||||||
|
self.epsilon = 1.0 / density
|
||||||
|
Mobject.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
class CompoundMobject(Mobject):
|
||||||
|
def __init__(self, *mobjects):
|
||||||
|
Mobject.__init__(self)
|
||||||
|
for mobject in mobjects:
|
||||||
|
self.add_points(mobject.points, mobject.rgbs)
|
||||||
|
|
||||||
|
|
||||||
|
###### Concrete Mobjects ########
|
||||||
|
|
||||||
|
class Stars(Mobject):
|
||||||
|
DEFAULT_COLOR = "white"
|
||||||
|
SHOULD_BUFF_POINTS = False
|
||||||
|
def __init__(self, num_points = DEFAULT_NUM_STARS,
|
||||||
|
*args, **kwargs):
|
||||||
|
self.num_points = num_points
|
||||||
|
Mobject.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
(
|
||||||
|
r * np.sin(phi)*np.cos(theta),
|
||||||
|
r * np.sin(phi)*np.sin(theta),
|
||||||
|
r * np.cos(phi)
|
||||||
|
)
|
||||||
|
for x in range(self.num_points)
|
||||||
|
for r, phi, theta in [[
|
||||||
|
max(SPACE_HEIGHT, SPACE_WIDTH) * random(),
|
||||||
|
np.pi * random(),
|
||||||
|
2 * np.pi * random(),
|
||||||
|
]]
|
||||||
|
])
|
||||||
|
|
||||||
|
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"
|
||||||
|
NUNGE_DISTANCE = 0.1
|
||||||
|
def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0),
|
||||||
|
length = 1, tip_length = 0.25,
|
||||||
|
normal = (0, 0, 1), *args, **kwargs):
|
||||||
|
self.point = np.array(point)
|
||||||
|
self.direction = np.array(direction) / np.linalg.norm(direction)
|
||||||
|
self.normal = np.array(normal)
|
||||||
|
self.length = length
|
||||||
|
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):
|
||||||
|
return self.shift(-self.direction * self.NUNGE_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"
|
||||||
|
def __init__(self, center = (0, 0, 0), radius = 0.05, *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(0, self.radius, self.epsilon)
|
||||||
|
for theta in np.arange(0, 2 * np.pi, self.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 *= np.linalg.norm(self.start - self.end)
|
||||||
|
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)
|
||||||
|
])
|
||||||
|
|
||||||
|
class CurvedLine(Line):
|
||||||
|
def generate_points(self):
|
||||||
|
equidistant_point = rotate_vector(
|
||||||
|
self.end - self.start,
|
||||||
|
np.pi/3, [0,0,1]
|
||||||
|
) + self.start
|
||||||
|
self.add_points([
|
||||||
|
(1 - t*(1-t))*(t*self.end + (1-t)*self.start) \
|
||||||
|
+ t*(1-t)*equidistant_point
|
||||||
|
for t in np.arange(0, 1, self.epsilon)
|
||||||
|
])
|
||||||
|
self.ep = equidistant_point
|
||||||
|
|
||||||
|
class CubeWithFaces(Mobject2D):
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
sgn * np.array(coords)
|
||||||
|
for x in np.arange(-1, 1, self.epsilon)
|
||||||
|
for y in np.arange(x, 1, self.epsilon)
|
||||||
|
for coords in it.permutations([x, y, 1])
|
||||||
|
for sgn in [-1, 1]
|
||||||
|
])
|
||||||
|
self.pose_at_angle()
|
||||||
|
|
||||||
|
def unit_normal(self, coords):
|
||||||
|
return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords))
|
||||||
|
|
||||||
|
class Cube(Mobject1D):
|
||||||
|
DEFAULT_COLOR = "yellow"
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]])
|
||||||
|
for p in [(0, 1, 2), (2, 0, 1), (1, 2, 0)]
|
||||||
|
for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon))
|
||||||
|
])
|
||||||
|
self.pose_at_angle()
|
||||||
|
|
||||||
|
|
||||||
|
class Sphere(Mobject2D):
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
(
|
||||||
|
np.sin(phi) * np.cos(theta),
|
||||||
|
np.sin(phi) * np.sin(theta),
|
||||||
|
np.cos(phi)
|
||||||
|
)
|
||||||
|
for phi in np.arange(self.epsilon, np.pi, self.epsilon)
|
||||||
|
for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi))
|
||||||
|
])
|
||||||
|
|
||||||
|
def unit_normal(self, coords):
|
||||||
|
return np.array(coords) / np.linalg.norm(coords)
|
||||||
|
|
||||||
|
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 FunctionGraph(Mobject1D):
|
||||||
|
DEFAULT_COLOR = "lightblue"
|
||||||
|
def __init__(self, function, x_range = [-10, 10], *args, **kwargs):
|
||||||
|
self.function = function
|
||||||
|
self.x_min = x_range[0] / SPACE_WIDTH
|
||||||
|
self.x_max = x_range[1] / SPACE_WIDTH
|
||||||
|
Mobject1D.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
scale_factor = 2.0 * SPACE_WIDTH / (self.x_max - self.x_min)
|
||||||
|
self.epsilon /= scale_factor
|
||||||
|
self.add_points([
|
||||||
|
np.array([x, self.function(x), 0])
|
||||||
|
for x in np.arange(self.x_min, self.x_max, self.epsilon)
|
||||||
|
])
|
||||||
|
self.scale(scale_factor)
|
||||||
|
|
||||||
|
|
||||||
|
class ParametricFunction(Mobject):
|
||||||
|
DEFAULT_COLOR = "lightblue"
|
||||||
|
def __init__(self,
|
||||||
|
function,
|
||||||
|
dim = 1,
|
||||||
|
expected_measure = 10.0,
|
||||||
|
density = None,
|
||||||
|
*args,
|
||||||
|
**kwargs):
|
||||||
|
self.function = function
|
||||||
|
self.dim = dim
|
||||||
|
self.expected_measure = expected_measure
|
||||||
|
if density:
|
||||||
|
self.epsilon = 1.0 / density
|
||||||
|
elif self.dim == 1:
|
||||||
|
self.epsilon = 1.0 / expected_measure / DEFAULT_POINT_DENSITY_1D
|
||||||
|
else:
|
||||||
|
self.epsilon = 1.0 / np.sqrt(expected_measure) / DEFAULT_POINT_DENSITY_2D
|
||||||
|
Mobject.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
if self.dim == 1:
|
||||||
|
self.add_points([
|
||||||
|
self.function(t)
|
||||||
|
for t in np.arange(-1, 1, self.epsilon)
|
||||||
|
])
|
||||||
|
if self.dim == 2:
|
||||||
|
self.add_points([
|
||||||
|
self.function(s, t)
|
||||||
|
for t in np.arange(-1, 1, self.epsilon)
|
||||||
|
for s in np.arange(-1, 1, self.epsilon)
|
||||||
|
])
|
||||||
|
|
||||||
|
class Grid(Mobject1D):
|
||||||
|
DEFAULT_COLOR = "green"
|
||||||
|
def __init__(self,
|
||||||
|
radius = max(SPACE_HEIGHT, SPACE_WIDTH),
|
||||||
|
interval_size = 1.0,
|
||||||
|
subinterval_size = 0.5,
|
||||||
|
*args, **kwargs):
|
||||||
|
self.radius = radius
|
||||||
|
self.interval_size = interval_size
|
||||||
|
self.subinterval_size = subinterval_size
|
||||||
|
Mobject1D.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
(sgns[0] * x, sgns[1] * y, 0)
|
||||||
|
for beta in np.arange(0, self.radius, self.interval_size)
|
||||||
|
for alpha in np.arange(0, self.radius, self.epsilon)
|
||||||
|
for sgns in it.product((-1, 1), (-1, 1))
|
||||||
|
for x, y in [(alpha, beta), (beta, alpha)]
|
||||||
|
])
|
||||||
|
if self.subinterval_size:
|
||||||
|
si = self.subinterval_size
|
||||||
|
color = Color(self.color)
|
||||||
|
color.set_rgb([x/2 for x in color.get_rgb()])
|
||||||
|
self.add_points([
|
||||||
|
(sgns[0] * x, sgns[1] * y, 0)
|
||||||
|
for beta in np.arange(0, self.radius, si)
|
||||||
|
if abs(beta % self.interval_size) > self.epsilon
|
||||||
|
for alpha in np.arange(0, self.radius, self.epsilon)
|
||||||
|
for sgns in it.product((-1, 1), (-1, 1))
|
||||||
|
for x, y in [(alpha, beta), (beta, alpha)]
|
||||||
|
], color = color)
|
||||||
|
|
||||||
|
class NumberLine(Mobject1D):
|
||||||
|
def __init__(self,
|
||||||
|
radius = SPACE_WIDTH,
|
||||||
|
interval_size = 0.5, tick_size = 0.1,
|
||||||
|
with_numbers = False, *args, **kwargs):
|
||||||
|
self.radius = int(radius) + 1
|
||||||
|
self.interval_size = interval_size
|
||||||
|
self.tick_size = tick_size
|
||||||
|
self.with_numbers = with_numbers
|
||||||
|
Mobject1D.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
self.add_points([
|
||||||
|
(x, 0, 0)
|
||||||
|
for x in np.arange(-self.radius, self.radius, self.epsilon)
|
||||||
|
])
|
||||||
|
self.add_points([
|
||||||
|
(0, y, 0)
|
||||||
|
for y in np.arange(-2*self.tick_size, 2*self.tick_size, self.epsilon)
|
||||||
|
])
|
||||||
|
self.add_points([
|
||||||
|
(x, y, 0)
|
||||||
|
for x in np.arange(-self.radius, self.radius, self.interval_size)
|
||||||
|
for y in np.arange(-self.tick_size, self.tick_size, self.epsilon)
|
||||||
|
])
|
||||||
|
if self.with_numbers:
|
||||||
|
#TODO, test
|
||||||
|
vertical_displacement = -0.3
|
||||||
|
nums = range(-self.radius, self.radius)
|
||||||
|
nums = map(lambda x : x / self.interval_size, nums)
|
||||||
|
mobs = tex_mobjects(*[str(num) for num in nums])
|
||||||
|
for num, mob in zip(nums, mobs):
|
||||||
|
mob.center().shift([num, vertical_displacement, 0])
|
||||||
|
self.add(*mobs)
|
||||||
|
|
||||||
|
# class ComplexPlane(Grid):
|
||||||
|
# def __init__(self, *args, **kwargs):
|
||||||
|
# Grid.__init__(self, *args, **kwargs)
|
||||||
|
# self.add(Dot())
|
@ -1,345 +0,0 @@
|
|||||||
from PIL import Image
|
|
||||||
from colour import Color
|
|
||||||
import numpy as np
|
|
||||||
import warnings
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import copy
|
|
||||||
import progressbar
|
|
||||||
import inspect
|
|
||||||
from images2gif import writeGif
|
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
from mobject import *
|
|
||||||
import displayer as disp
|
|
||||||
|
|
||||||
class MobjectMovement(object):
|
|
||||||
def __init__(self,
|
|
||||||
mobject,
|
|
||||||
run_time = DEFAULT_ANIMATION_RUN_TIME,
|
|
||||||
alpha_func = high_inflection_0_to_1,
|
|
||||||
name = None):
|
|
||||||
if isinstance(mobject, type) and issubclass(mobject, Mobject):
|
|
||||||
self.mobject = mobject()
|
|
||||||
elif isinstance(mobject, Mobject):
|
|
||||||
self.mobject = mobject
|
|
||||||
else:
|
|
||||||
raise Exception("Invalid mobject parameter, must be \
|
|
||||||
subclass or instance of Mobject")
|
|
||||||
self.starting_mobject = copy.deepcopy(self.mobject)
|
|
||||||
self.reference_mobjects = [self.starting_mobject]
|
|
||||||
self.alpha_func = alpha_func or (lambda x : x)
|
|
||||||
self.run_time = run_time
|
|
||||||
#TODO, Adress the idea of filtering the mobmov
|
|
||||||
self.filter_functions = []
|
|
||||||
self.restricted_height = SPACE_HEIGHT
|
|
||||||
self.restricted_width = SPACE_WIDTH
|
|
||||||
self.spacial_center = np.zeros(3)
|
|
||||||
self.name = name or self.__class__.__name__ + str(self.mobject)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_points_and_rgbs(self):
|
|
||||||
"""
|
|
||||||
It is the responsibility of this class to only emit points within
|
|
||||||
the space. Returns np array of points and corresponding np array
|
|
||||||
of rgbs
|
|
||||||
"""
|
|
||||||
#TODO, I don't think this should be necessary. This should happen
|
|
||||||
#under the individual mobjects.
|
|
||||||
points = self.mobject.points
|
|
||||||
rgbs = self.mobject.rgbs
|
|
||||||
#Filters out what is out of bounds.
|
|
||||||
admissibles = (abs(points[:,0]) < self.restricted_width) * \
|
|
||||||
(abs(points[:,1]) < self.restricted_height)
|
|
||||||
for filter_function in self.filter_functions:
|
|
||||||
admissibles *= ~filter_function(points)
|
|
||||||
if any(self.spacial_center):
|
|
||||||
points += self.spacial_center
|
|
||||||
#Filter out points pushed off the edge
|
|
||||||
admissibles *= (abs(points[:,0]) < SPACE_WIDTH) * \
|
|
||||||
(abs(points[:,1]) < SPACE_HEIGHT)
|
|
||||||
if rgbs.shape[0] < points.shape[0]:
|
|
||||||
#TODO, this shouldn't be necessary, find what's happening.
|
|
||||||
points = points[:rgbs.shape[0], :]
|
|
||||||
admissibles = admissibles[:rgbs.shape[0]]
|
|
||||||
return points[admissibles, :], rgbs[admissibles, :]
|
|
||||||
|
|
||||||
def update(self, alpha):
|
|
||||||
if alpha < 0:
|
|
||||||
alpha = 0
|
|
||||||
if alpha > 1:
|
|
||||||
alpha = 1
|
|
||||||
self.update_mobject(self.alpha_func(alpha))
|
|
||||||
|
|
||||||
def filter_out(self, *filter_functions):
|
|
||||||
self.filter_functions += filter_functions
|
|
||||||
return self
|
|
||||||
|
|
||||||
def restrict_height(self, height):
|
|
||||||
self.restricted_height = min(height, SPACE_HEIGHT)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def restrict_width(self, width):
|
|
||||||
self.restricted_width = min(width, SPACE_WIDTH)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def shift(self, vector):
|
|
||||||
self.spacial_center += vector
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_run_time(self, time):
|
|
||||||
self.run_time = time
|
|
||||||
return self.reload()
|
|
||||||
|
|
||||||
def set_alpha_func(self, alpha_func):
|
|
||||||
if alpha_func is None:
|
|
||||||
alpha_func = lambda x : x
|
|
||||||
self.alpha_func = alpha_func
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_name(self, name):
|
|
||||||
self.name = name
|
|
||||||
return self
|
|
||||||
|
|
||||||
# def drag_pixels(self):
|
|
||||||
# self.frames = drag_pixels(self.get_frames())
|
|
||||||
# return self
|
|
||||||
|
|
||||||
# def reverse(self):
|
|
||||||
# self.get_frames().reverse()
|
|
||||||
# self.name = 'Reversed' + str(self)
|
|
||||||
# return self
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
#Typically ipmlemented by subclass
|
|
||||||
pass
|
|
||||||
|
|
||||||
def clean_up(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
###### Concrete MobjectMovement ########
|
|
||||||
|
|
||||||
class Rotating(MobjectMovement):
|
|
||||||
def __init__(self,
|
|
||||||
mobject,
|
|
||||||
axis = None,
|
|
||||||
axes = [[0, 0, 1], [0, 1, 0]],
|
|
||||||
radians = 2 * np.pi,
|
|
||||||
run_time = 20.0,
|
|
||||||
alpha_func = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
MobjectMovement.__init__(
|
|
||||||
self, mobject,
|
|
||||||
run_time = run_time,
|
|
||||||
alpha_func = alpha_func,
|
|
||||||
*args, **kwargs
|
|
||||||
)
|
|
||||||
self.axes = [axis] if axis else axes
|
|
||||||
self.radians = radians
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
self.mobject.points = self.starting_mobject.points
|
|
||||||
for axis in self.axes:
|
|
||||||
self.mobject.rotate(
|
|
||||||
self.radians * alpha,
|
|
||||||
axis
|
|
||||||
)
|
|
||||||
|
|
||||||
class RotationAsTransform(Rotating):
|
|
||||||
def __init__(self, mobject, radians, axis = (0, 0, 1), axes = None,
|
|
||||||
run_time = DEFAULT_ANIMATION_RUN_TIME,
|
|
||||||
alpha_func = high_inflection_0_to_1,
|
|
||||||
*args, **kwargs):
|
|
||||||
Rotating.__init__(
|
|
||||||
self,
|
|
||||||
mobject,
|
|
||||||
axis = axis,
|
|
||||||
axes = axes,
|
|
||||||
run_time = run_time,
|
|
||||||
radians = radians,
|
|
||||||
alpha_func = alpha_func,
|
|
||||||
)
|
|
||||||
|
|
||||||
class FadeOut(MobjectMovement):
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
self.mobject.rgbs = self.starting_mobject.rgbs * (1 - alpha)
|
|
||||||
|
|
||||||
class Reveal(MobjectMovement):
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
self.mobject.rgbs = self.starting_mobject.rgbs * alpha
|
|
||||||
if self.mobject.points.shape != self.starting_mobject.points.shape:
|
|
||||||
self.mobject.points = self.starting_mobject.points
|
|
||||||
#TODO, Why do you need to do this? Shouldn't points always align?
|
|
||||||
|
|
||||||
class Transform(MobjectMovement):
|
|
||||||
def __init__(self, mobject1, mobject2,
|
|
||||||
run_time = DEFAULT_TRANSFORM_RUN_TIME,
|
|
||||||
*args, **kwargs):
|
|
||||||
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
|
|
||||||
Mobject.align_data(mobject1, mobject2)
|
|
||||||
MobjectMovement.__init__(self, mobject1, run_time = run_time, *args, **kwargs)
|
|
||||||
self.ending_mobject = mobject2
|
|
||||||
self.mobject.SHOULD_BUFF_POINTS = \
|
|
||||||
mobject1.SHOULD_BUFF_POINTS and mobject2.SHOULD_BUFF_POINTS
|
|
||||||
self.reference_mobjects.append(mobject2)
|
|
||||||
self.name += "To" + str(mobject2)
|
|
||||||
|
|
||||||
if count2 < count1:
|
|
||||||
#Ensure redundant pixels fade to black
|
|
||||||
indices = self.non_redundant_m2_indices = \
|
|
||||||
np.arange(0, count1-1, float(count1) / count2).astype('int')
|
|
||||||
temp = np.zeros(mobject2.points.shape)
|
|
||||||
temp[indices] = mobject2.rgbs[indices]
|
|
||||||
mobject2.rgbs = temp
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
Mobject.interpolate(
|
|
||||||
self.starting_mobject,
|
|
||||||
self.ending_mobject,
|
|
||||||
self.mobject,
|
|
||||||
alpha
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean_up(self):
|
|
||||||
if hasattr(self, "non_redundant_m2_indices"):
|
|
||||||
#Reduce mobject (which has become identical to mobject2), as
|
|
||||||
#well as mobject2 itself
|
|
||||||
for mobject in [self.mobject, self.ending_mobject]:
|
|
||||||
for attr in ['points', 'rgbs']:
|
|
||||||
setattr(
|
|
||||||
mobject, attr,
|
|
||||||
getattr(
|
|
||||||
self.ending_mobject,
|
|
||||||
attr
|
|
||||||
)[self.non_redundant_m2_indices]
|
|
||||||
)
|
|
||||||
|
|
||||||
class ApplyMethod(Transform):
|
|
||||||
def __init__(self, method, mobject, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Method is a method of Mobject
|
|
||||||
"""
|
|
||||||
method_args = ()
|
|
||||||
if isinstance(method, tuple):
|
|
||||||
method, method_args = method[0], method[1:]
|
|
||||||
if not inspect.ismethod(method):
|
|
||||||
raise "Not a valid Mobject method"
|
|
||||||
Transform.__init__(
|
|
||||||
self,
|
|
||||||
mobject,
|
|
||||||
method(copy.deepcopy(mobject), *method_args),
|
|
||||||
*args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
class ApplyFunction(Transform):
|
|
||||||
def __init__(self, function, mobject, run_time = DEFAULT_ANIMATION_RUN_TIME,
|
|
||||||
*args, **kwargs):
|
|
||||||
map_image = copy.deepcopy(mobject)
|
|
||||||
map_image.points = np.array(map(function, map_image.points))
|
|
||||||
Transform.__init__(self, mobject, map_image, run_time = run_time,
|
|
||||||
*args, **kwargs)
|
|
||||||
self.name = "".join([
|
|
||||||
"Apply",
|
|
||||||
"".join([s.capitalize() for s in function.__name__.split("_")]),
|
|
||||||
"To" + str(mobject)
|
|
||||||
])
|
|
||||||
|
|
||||||
class ComplexFunction(ApplyFunction):
|
|
||||||
def __init__(self, function, *args, **kwargs):
|
|
||||||
def point_map(point):
|
|
||||||
x, y, z = point
|
|
||||||
c = np.complex(x, y)
|
|
||||||
c = function(c)
|
|
||||||
return c.real, c.imag, z
|
|
||||||
if len(args) > 0:
|
|
||||||
args = list(args)
|
|
||||||
mobject = args.pop(0)
|
|
||||||
elif "mobject" in kwargs:
|
|
||||||
mobject = kwargs.pop("mobject")
|
|
||||||
else:
|
|
||||||
mobject = Grid()
|
|
||||||
ApplyFunction.__init__(self, point_map, mobject, *args, **kwargs)
|
|
||||||
self.name = "ComplexFunction" + to_cammel_case(function.__name__)
|
|
||||||
#Todo, abstract away function naming'
|
|
||||||
|
|
||||||
class Homotopy(MobjectMovement):
|
|
||||||
def __init__(self, homotopy, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Homotopy a function from (x, y, z, t) to (x', y', z')
|
|
||||||
"""
|
|
||||||
self.homotopy = homotopy
|
|
||||||
MobjectMovement.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
self.mobject.points = np.array([
|
|
||||||
self.homotopy((x, y, z, alpha))
|
|
||||||
for x, y, z in self.starting_mobject.points
|
|
||||||
])
|
|
||||||
|
|
||||||
class ComplexHomotopy(Homotopy):
|
|
||||||
def __init__(self, complex_homotopy, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Complex Hootopy a function (z, t) to z'
|
|
||||||
"""
|
|
||||||
def homotopy((x, y, z, t)):
|
|
||||||
c = complex_homotopy((complex(x, y), t))
|
|
||||||
return (c.real, c.imag, z)
|
|
||||||
if len(args) > 0:
|
|
||||||
args = list(args)
|
|
||||||
mobject = args.pop(0)
|
|
||||||
elif "mobject" in kwargs:
|
|
||||||
mobject = kwargs["mobject"]
|
|
||||||
else:
|
|
||||||
mobject = Grid()
|
|
||||||
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
|
|
||||||
self.name = "ComplexHomotopy" + \
|
|
||||||
to_cammel_case(complex_homotopy.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ShowCreation(MobjectMovement):
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
#TODO, shoudl I make this more efficient?
|
|
||||||
new_num_points = int(alpha * self.starting_mobject.points.shape[0])
|
|
||||||
for attr in ["points", "rgbs"]:
|
|
||||||
setattr(
|
|
||||||
self.mobject,
|
|
||||||
attr,
|
|
||||||
getattr(self.starting_mobject, attr)[:new_num_points, :]
|
|
||||||
)
|
|
||||||
|
|
||||||
class Flash(MobjectMovement):
|
|
||||||
def __init__(self, mobject, color = "white", slow_factor = 0.01,
|
|
||||||
run_time = 0.1, alpha_func = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
MobjectMovement.__init__(self, mobject, run_time = run_time,
|
|
||||||
alpha_func = alpha_func,
|
|
||||||
*args, **kwargs)
|
|
||||||
self.intermediate = Mobject(color = color)
|
|
||||||
self.intermediate.add_points([
|
|
||||||
point + (x, y, 0)
|
|
||||||
for point in self.mobject.points
|
|
||||||
for x in [-1, 1]
|
|
||||||
for y in [-1, 1]
|
|
||||||
])
|
|
||||||
self.reference_mobjects.append(self.intermediate)
|
|
||||||
self.slow_factor = slow_factor
|
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
|
||||||
#Makes alpha go from 0 to slow_factor to 0 instead of 0 to 1
|
|
||||||
alpha = self.slow_factor * (1.0 - 4 * (alpha - 0.5)**2)
|
|
||||||
Mobject.interpolate(
|
|
||||||
self.starting_mobject,
|
|
||||||
self.intermediate,
|
|
||||||
self.mobject,
|
|
||||||
alpha
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
132
moser/main.py
132
moser/main.py
@ -17,57 +17,9 @@ from scene import Scene
|
|||||||
from moser_helpers import *
|
from moser_helpers import *
|
||||||
from graphs import *
|
from graphs import *
|
||||||
|
|
||||||
RADIUS = SPACE_HEIGHT - 0.1
|
|
||||||
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
|
||||||
|
|
||||||
movie_prefix = "moser/"
|
movie_prefix = "moser/"
|
||||||
|
|
||||||
############################################
|
|
||||||
|
|
||||||
class CircleScene(Scene):
|
|
||||||
def __init__(self, radians, *args, **kwargs):
|
|
||||||
Scene.__init__(self, *args, **kwargs)
|
|
||||||
self.radius = RADIUS
|
|
||||||
self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius)
|
|
||||||
self.points = [
|
|
||||||
(self.radius * np.cos(angle), self.radius * np.sin(angle), 0)
|
|
||||||
for angle in radians
|
|
||||||
]
|
|
||||||
self.dots = [Dot(point) for point in self.points]
|
|
||||||
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
|
|
||||||
self.add(self.circle, *self.dots + self.lines)
|
|
||||||
|
|
||||||
class GraphScene(Scene):
|
|
||||||
#Note, the placement of vertices in this is pretty hard coded, be
|
|
||||||
#warned if you want to change it.
|
|
||||||
def __init__(self, graph, *args, **kwargs):
|
|
||||||
Scene.__init__(self, *args, **kwargs)
|
|
||||||
#See CUBE_GRAPH above for format of graph
|
|
||||||
self.graph = graph
|
|
||||||
self.points = map(np.array, graph["vertices"])
|
|
||||||
self.vertices = self.dots = [Dot(p) for p in self.points]
|
|
||||||
self.edges = [
|
|
||||||
Line(self.points[i], self.points[j])
|
|
||||||
for i, j in graph["edges"]
|
|
||||||
]
|
|
||||||
self.add(*self.dots + self.edges)
|
|
||||||
|
|
||||||
def generate_regions(self):
|
|
||||||
regions = [
|
|
||||||
region_from_line_boundary(*[
|
|
||||||
[
|
|
||||||
self.points[rc[i]],
|
|
||||||
self.points[rc[(i+1)%len(rc)]]
|
|
||||||
]
|
|
||||||
for i in range(len(rc))
|
|
||||||
])
|
|
||||||
for rc in self.graph["region_cycles"]
|
|
||||||
]
|
|
||||||
regions[-1].complement()#Outer region painted outwardly...
|
|
||||||
self.regions = regions
|
|
||||||
|
|
||||||
##################################################
|
|
||||||
|
|
||||||
def count_lines(*radians):
|
def count_lines(*radians):
|
||||||
#TODO, Count things explicitly?
|
#TODO, Count things explicitly?
|
||||||
sc = CircleScene(radians)
|
sc = CircleScene(radians)
|
||||||
@ -378,7 +330,7 @@ def quadruplets_to_intersections(*radians):
|
|||||||
))
|
))
|
||||||
# sc.remove(arrows)
|
# sc.remove(arrows)
|
||||||
|
|
||||||
name = "QuadrupletsToIntersections" + len(radians)
|
name = "QuadrupletsToIntersections%d"%len(radians)
|
||||||
sc.write_to_movie(movie_prefix + name)
|
sc.write_to_movie(movie_prefix + name)
|
||||||
|
|
||||||
def defining_graph(graph):
|
def defining_graph(graph):
|
||||||
@ -447,7 +399,7 @@ def eulers_formula(graph):
|
|||||||
])
|
])
|
||||||
for mob in form.values():
|
for mob in form.values():
|
||||||
mob.shift((0, SPACE_HEIGHT-1.5, 0))
|
mob.shift((0, SPACE_HEIGHT-1.5, 0))
|
||||||
formula = CompoundMobject(*form.values())
|
formula = CompoundMobject(*[form[k] for k in form.keys() if k != "=2"])
|
||||||
new_form = dict([
|
new_form = dict([
|
||||||
(key, deepcopy(mob).shift((0, -0.7, 0)))
|
(key, deepcopy(mob).shift((0, -0.7, 0)))
|
||||||
for key, mob in zip(form.keys(), form.values())
|
for key, mob in zip(form.keys(), form.values())
|
||||||
@ -486,6 +438,81 @@ def eulers_formula(graph):
|
|||||||
name = "EulersFormula" + graph["name"]
|
name = "EulersFormula" + graph["name"]
|
||||||
gs.write_to_movie(movie_prefix + name)
|
gs.write_to_movie(movie_prefix + name)
|
||||||
|
|
||||||
|
def apply_euler_to_moser(*radians):
|
||||||
|
cs = CircleScene(radians)
|
||||||
|
cs.remove(cs.n_equals)
|
||||||
|
n_equals, intersection_count = tex_mobjects([
|
||||||
|
r"&n = %d\\"%len(radians),
|
||||||
|
r"&{%d \choose 4} = %d"%(len(radians), choose(len(radians), 4))
|
||||||
|
])
|
||||||
|
shift_val = cs.n_equals.get_center() - n_equals.get_center()
|
||||||
|
for mob in n_equals, intersection_count:
|
||||||
|
mob.shift(shift_val)
|
||||||
|
cs.add(n_equals)
|
||||||
|
yellow_dots = [d.highlight("yellow") for d in deepcopy(cs.dots)]
|
||||||
|
yellow_lines = [l.highlight("yellow") for l in deepcopy(cs.lines)]
|
||||||
|
cs.animate(*[
|
||||||
|
ShowCreation(dot) for dot in yellow_dots
|
||||||
|
], run_time = 1.0)
|
||||||
|
cs.dither()
|
||||||
|
cs.remove(*yellow_dots)
|
||||||
|
cs.animate(*[
|
||||||
|
ShowCreation(line) for line in yellow_lines
|
||||||
|
], run_time = 1.0)
|
||||||
|
cs.dither()
|
||||||
|
cs.remove(yellow_lines)
|
||||||
|
cannot_intersect = text_mobject(r"""
|
||||||
|
Euler's formula does not apply to \\
|
||||||
|
graphs whose edges intersect!
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
cannot_intersect.center()
|
||||||
|
for mob in cs.mobjects:
|
||||||
|
mob.fade(0.3)
|
||||||
|
cs.add(cannot_intersect)
|
||||||
|
cs.dither()
|
||||||
|
cs.remove(cannot_intersect)
|
||||||
|
for mob in cs.mobjects:
|
||||||
|
mob.fade(1/0.3)
|
||||||
|
cs.generate_intersection_dots()
|
||||||
|
cs.animate(FadeIn(intersection_count), *[
|
||||||
|
ShowCreation(dot) for dot in cs.intersection_dots
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
name = "ApplyEulerToMoser%d"%len(radians)
|
||||||
|
cs.write_to_movie(movie_prefix + name)
|
||||||
|
|
||||||
|
def show_moser_graph_lines(*radians):
|
||||||
|
radians = list(set(map(lambda x : x%(2*np.pi), radians)))
|
||||||
|
radians.sort()
|
||||||
|
|
||||||
|
cs = CircleScene(radians)
|
||||||
|
cs.chop_lines_at_intersection_points()
|
||||||
|
cs.add(*cs.intersection_dots)
|
||||||
|
small_lines = [
|
||||||
|
deepcopy(line).scale_in_place(0.5)
|
||||||
|
for line in cs.lines
|
||||||
|
]
|
||||||
|
cs.animate(*[
|
||||||
|
Transform(line, small_line, run_time = 3.0)
|
||||||
|
for line, small_line in zip(cs.lines, small_lines)
|
||||||
|
])
|
||||||
|
cs.count(cs.lines, color = "yellow",
|
||||||
|
run_time = 9.0, num_offset = (0, 0, 0))
|
||||||
|
cs.dither()
|
||||||
|
cs.remove(cs.number)
|
||||||
|
cs.chop_circle_at_points()
|
||||||
|
cs.animate(*[
|
||||||
|
Transform(p, sp, run_time = 3.0)
|
||||||
|
for p, sp in zip(cs.circle_pieces, cs.smaller_circle_pieces)
|
||||||
|
])
|
||||||
|
cs.count(cs.circle_pieces, color = "yellow",
|
||||||
|
run_time = 2.0, num_offset = (0, 0, 0))
|
||||||
|
name = "ShowMoserGraphLines%d"%len(radians)
|
||||||
|
cs.write_to_movie(movie_prefix + name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
@ -503,13 +530,14 @@ if __name__ == '__main__':
|
|||||||
# illustrate_n_choose_k(6, 4)
|
# illustrate_n_choose_k(6, 4)
|
||||||
# intersection_point_correspondances(radians, range(0, 7, 2))
|
# intersection_point_correspondances(radians, range(0, 7, 2))
|
||||||
# lines_intersect_outside(radians, [2, 4, 5, 6])
|
# lines_intersect_outside(radians, [2, 4, 5, 6])
|
||||||
quadruplets_to_intersections(*radians[:6])
|
# quadruplets_to_intersections(*radians[:6])
|
||||||
# defining_graph(SAMPLE_GRAPH)
|
# defining_graph(SAMPLE_GRAPH)
|
||||||
# doubled_edges(CUBE_GRAPH)
|
# doubled_edges(CUBE_GRAPH)
|
||||||
# eulers_formula(CUBE_GRAPH)
|
# eulers_formula(CUBE_GRAPH)
|
||||||
# eulers_formula(SAMPLE_GRAPH)
|
# eulers_formula(SAMPLE_GRAPH)
|
||||||
# eulers_formula(OCTOHEDRON_GRAPH)
|
# eulers_formula(OCTOHEDRON_GRAPH)
|
||||||
|
# apply_euler_to_moser(*radians)
|
||||||
|
show_moser_graph_lines(*radians[:6])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,125 @@ import itertools as it
|
|||||||
|
|
||||||
from constants import *
|
from constants import *
|
||||||
from image_mobject import *
|
from image_mobject import *
|
||||||
|
from scene import Scene
|
||||||
|
|
||||||
|
RADIUS = SPACE_HEIGHT - 0.1
|
||||||
|
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
||||||
|
|
||||||
|
############################################
|
||||||
|
|
||||||
|
class CircleScene(Scene):
|
||||||
|
def __init__(self, radians, *args, **kwargs):
|
||||||
|
Scene.__init__(self, *args, **kwargs)
|
||||||
|
self.radius = RADIUS
|
||||||
|
self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius)
|
||||||
|
self.points = [
|
||||||
|
(self.radius * np.cos(angle), self.radius * np.sin(angle), 0)
|
||||||
|
for angle in radians
|
||||||
|
]
|
||||||
|
self.dots = [Dot(point) for point in self.points]
|
||||||
|
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
|
||||||
|
self.n_equals = tex_mobject(
|
||||||
|
"n=%d"%len(radians),
|
||||||
|
size = r"\small"
|
||||||
|
).shift((-SPACE_WIDTH+1, SPACE_HEIGHT-1.5, 0))
|
||||||
|
self.add(self.circle, self.n_equals, *self.dots + self.lines)
|
||||||
|
|
||||||
|
def generate_intersection_dots(self):
|
||||||
|
"""
|
||||||
|
Generates and adds attributes intersection_points and
|
||||||
|
intersection_dots, but does not yet add them to the scene
|
||||||
|
"""
|
||||||
|
self.intersection_points = [
|
||||||
|
intersection((p[0], p[2]), (p[1], p[3]))
|
||||||
|
for p in it.combinations(self.points, 4)
|
||||||
|
]
|
||||||
|
self.intersection_dots = [
|
||||||
|
Dot(point) for point in self.intersection_points
|
||||||
|
]
|
||||||
|
|
||||||
|
def chop_lines_at_intersection_points(self):
|
||||||
|
if not hasattr(self, "intersection_dots"):
|
||||||
|
self.generate_intersection_dots()
|
||||||
|
self.remove(*self.lines)
|
||||||
|
self.lines = []
|
||||||
|
for point_pair in it.combinations(self.points, 2):
|
||||||
|
int_points = filter(
|
||||||
|
lambda p : is_on_line(p, *point_pair),
|
||||||
|
self.intersection_points
|
||||||
|
)
|
||||||
|
points = list(point_pair) + int_points
|
||||||
|
points = map(lambda p : (p[0], p[1], 0), points)
|
||||||
|
points.sort(cmp = lambda x,y: cmp(x[0], y[0]))
|
||||||
|
self.lines += [
|
||||||
|
Line(points[i], points[i+1])
|
||||||
|
for i in range(len(points)-1)
|
||||||
|
]
|
||||||
|
self.add(*self.lines)
|
||||||
|
|
||||||
|
def chop_circle_at_points(self):
|
||||||
|
self.remove(self.circle)
|
||||||
|
self.circle_pieces = []
|
||||||
|
self.smaller_circle_pieces = []
|
||||||
|
for i in range(len(self.points)):
|
||||||
|
pp = self.points[i], self.points[(i+1)%len(self.points)]
|
||||||
|
transform = np.array([
|
||||||
|
[pp[0][0], pp[1][0], 0],
|
||||||
|
[pp[0][1], pp[1][1], 0],
|
||||||
|
[0, 0, 1]
|
||||||
|
])
|
||||||
|
circle = deepcopy(self.circle)
|
||||||
|
smaller_circle = deepcopy(self.circle)
|
||||||
|
for c in circle, smaller_circle:
|
||||||
|
c.points = np.dot(
|
||||||
|
c.points,
|
||||||
|
np.transpose(np.linalg.inv(transform))
|
||||||
|
)
|
||||||
|
c.filter_out(
|
||||||
|
lambda p : p[0] < 0 or p[1] < 0
|
||||||
|
)
|
||||||
|
if c == smaller_circle:
|
||||||
|
c.filter_out(
|
||||||
|
lambda p : p[0] > 4*p[1] or p[1] > 4*p[0]
|
||||||
|
)
|
||||||
|
c.points = np.dot(
|
||||||
|
c.points,
|
||||||
|
np.transpose(transform)
|
||||||
|
)
|
||||||
|
self.circle_pieces.append(circle)
|
||||||
|
self.smaller_circle_pieces.append(smaller_circle)
|
||||||
|
self.add(*self.circle_pieces)
|
||||||
|
|
||||||
|
class GraphScene(Scene):
|
||||||
|
#Note, the placement of vertices in this is pretty hard coded, be
|
||||||
|
#warned if you want to change it.
|
||||||
|
def __init__(self, graph, *args, **kwargs):
|
||||||
|
Scene.__init__(self, *args, **kwargs)
|
||||||
|
#See CUBE_GRAPH above for format of graph
|
||||||
|
self.graph = graph
|
||||||
|
self.points = map(np.array, graph["vertices"])
|
||||||
|
self.vertices = self.dots = [Dot(p) for p in self.points]
|
||||||
|
self.edges = [
|
||||||
|
Line(self.points[i], self.points[j])
|
||||||
|
for i, j in graph["edges"]
|
||||||
|
]
|
||||||
|
self.add(*self.dots + self.edges)
|
||||||
|
|
||||||
|
def generate_regions(self):
|
||||||
|
regions = [
|
||||||
|
region_from_line_boundary(*[
|
||||||
|
[
|
||||||
|
self.points[rc[i]],
|
||||||
|
self.points[rc[(i+1)%len(rc)]]
|
||||||
|
]
|
||||||
|
for i in range(len(rc))
|
||||||
|
])
|
||||||
|
for rc in self.graph["region_cycles"]
|
||||||
|
]
|
||||||
|
regions[-1].complement()#Outer region painted outwardly...
|
||||||
|
self.regions = regions
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
|
||||||
def choose(n, r):
|
def choose(n, r):
|
||||||
if n < r: return 0
|
if n < r: return 0
|
||||||
@ -15,6 +134,16 @@ def choose(n, r):
|
|||||||
def moser_function(n):
|
def moser_function(n):
|
||||||
return choose(n, 4) + choose(n, 2) + 1
|
return choose(n, 4) + choose(n, 2) + 1
|
||||||
|
|
||||||
|
def is_on_line(p0, p1, p2, threshold = 0.01):
|
||||||
|
"""
|
||||||
|
Returns true of p0 is on the line between p1 and p2
|
||||||
|
"""
|
||||||
|
p0, p1, p2 = map(lambda tup : np.array(tup[:2]), [p0, p1, p2])
|
||||||
|
p1 -= p0
|
||||||
|
p2 -= p0
|
||||||
|
return abs((p1[0] / p1[1]) - (p2[0] / p2[1])) < threshold
|
||||||
|
|
||||||
|
|
||||||
def intersection(line1, line2):
|
def intersection(line1, line2):
|
||||||
"""
|
"""
|
||||||
A "line" should come in the form [(x0, y0), (x1, y1)] for two
|
A "line" should come in the form [(x0, y0), (x1, y1)] for two
|
||||||
|
@ -3,7 +3,9 @@ import itertools as it
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from constants import *
|
from constants import *
|
||||||
|
|
||||||
def tex_to_image(expression, size = "\HUGE"):
|
def tex_to_image(expression,
|
||||||
|
size = "\HUGE",
|
||||||
|
template_tex_file = TEMPLATE_TEX_FILE):
|
||||||
"""
|
"""
|
||||||
Returns list of images for correpsonding with a list of expressions
|
Returns list of images for correpsonding with a list of expressions
|
||||||
"""
|
"""
|
||||||
@ -27,7 +29,7 @@ def tex_to_image(expression, size = "\HUGE"):
|
|||||||
"at size %s to "%(size),
|
"at size %s to "%(size),
|
||||||
filestem,
|
filestem,
|
||||||
])
|
])
|
||||||
with open(TEMPLATE_TEX_FILE, "r") as infile:
|
with open(template_tex_file, "r") as infile:
|
||||||
body = infile.read()
|
body = infile.read()
|
||||||
body = body.replace(SIZE_TO_REPLACE, size)
|
body = body.replace(SIZE_TO_REPLACE, size)
|
||||||
body = body.replace(TEX_TEXT_TO_REPLACE, expression)
|
body = body.replace(TEX_TEXT_TO_REPLACE, expression)
|
||||||
|
Reference in New Issue
Block a user