mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Some graph utilities implemented for Moser videos
This commit is contained in:
parent
5aff0b6374
commit
8d5240e070
10 changed files with 565 additions and 50 deletions
22
animation.py
22
animation.py
|
@ -11,7 +11,7 @@ from images2gif import writeGif
|
|||
|
||||
from helpers import *
|
||||
from constants import *
|
||||
from mobject import Mobject
|
||||
from mobject import Mobject, Point
|
||||
|
||||
class Animation(object):
|
||||
def __init__(self,
|
||||
|
@ -179,6 +179,9 @@ class Transform(Animation):
|
|||
run_time = DEFAULT_TRANSFORM_RUN_TIME,
|
||||
*args, **kwargs):
|
||||
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
|
||||
if count2 == 0:
|
||||
mobject2 = Point((SPACE_WIDTH, SPACE_HEIGHT, 0))
|
||||
count2 = mobject2.get_num_points()
|
||||
Mobject.align_data(mobject1, mobject2)
|
||||
Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs)
|
||||
self.ending_mobject = mobject2
|
||||
|
@ -189,11 +192,13 @@ class Transform(Animation):
|
|||
|
||||
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')
|
||||
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
|
||||
self.non_redundant_m2_indices = indices
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Mobject.interpolate(
|
||||
|
@ -222,6 +227,17 @@ class FadeToColor(Transform):
|
|||
target = copy.deepcopy(mobject).highlight(color)
|
||||
Transform.__init__(self, mobject, target, *args, **kwargs)
|
||||
|
||||
class Highlight(FadeToColor):
|
||||
def __init__(self, mobject, color = "red",
|
||||
run_time = DEFAULT_ANIMATION_RUN_TIME,
|
||||
alpha_func = there_and_back, *args, **kwargs):
|
||||
FadeToColor.__init__(
|
||||
self, mobject, color,
|
||||
run_time = run_time,
|
||||
alpha_func = alpha_func,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
class ScaleInPlace(Transform):
|
||||
def __init__(self, mobject, scale_factor, *args, **kwargs):
|
||||
target = copy.deepcopy(mobject)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
|
||||
PRODUCTION_QUALITY = True
|
||||
GENERALLY_BUFF_POINTS = True
|
||||
GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
|
||||
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50
|
||||
|
|
|
@ -59,6 +59,8 @@ def make_even_by_cycling(iterable_1, iterable_2):
|
|||
def sigmoid(x):
|
||||
return 1.0/(1 + np.exp(-x))
|
||||
|
||||
### Alpha Functions ###
|
||||
|
||||
def high_inflection_0_to_1(t, inflection = 10.0):
|
||||
error = sigmoid(-inflection / 2)
|
||||
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
|
||||
|
@ -67,6 +69,10 @@ def there_and_back(t, inflection = 10.0):
|
|||
new_t = 2*t if t < 0.5 else 2*(1 - t)
|
||||
return high_inflection_0_to_1(new_t, inflection)
|
||||
|
||||
def not_quite_there(t, proportion = 0.7):
|
||||
return proportion*high_inflection_0_to_1(t)
|
||||
|
||||
### Functional Functions ###
|
||||
|
||||
def composition(func_list):
|
||||
"""
|
||||
|
@ -124,8 +130,7 @@ def z_to_vector(vector):
|
|||
return np.dot(rotation_about_z(theta), phi_down)
|
||||
|
||||
def rotate_vector(vector, angle, axis):
|
||||
#Slightly hacky, changes vector in place
|
||||
vector[:3] = np.dot(rotation_matrix(angle, axis), vector)
|
||||
return np.dot(rotation_matrix(angle, axis), vector)
|
||||
|
||||
def angle_between(v1, v2):
|
||||
return np.arccos(np.dot(
|
||||
|
|
|
@ -86,7 +86,7 @@ class VideoIcon(ImageMobject):
|
|||
ImageMobject.__init__(self, "video_icon", *args, **kwargs)
|
||||
self.scale(0.3)
|
||||
|
||||
|
||||
#Purely redundant function to make singulars and plurals sensible
|
||||
def tex_mobject(expression, size = "\HUGE"):
|
||||
return tex_mobjects(expression, size)
|
||||
|
||||
|
|
37
mobject.py
37
mobject.py
|
@ -89,13 +89,14 @@ class Mobject(object):
|
|||
self.shift(-self.get_center())
|
||||
return self
|
||||
|
||||
def get_center(self):
|
||||
return np.apply_along_axis(np.mean, 0, self.points)
|
||||
|
||||
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)
|
||||
|
@ -146,6 +147,17 @@ class Mobject(object):
|
|||
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
|
||||
|
@ -228,6 +240,7 @@ class Point(Mobject):
|
|||
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,
|
||||
|
@ -246,7 +259,7 @@ class Arrow(Mobject1D):
|
|||
])
|
||||
tips_dir = np.array(-self.direction), np.array(-self.direction)
|
||||
for i, sgn in zip([0, 1], [-1, 1]):
|
||||
rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
|
||||
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)
|
||||
|
@ -271,13 +284,13 @@ class Dot(Mobject1D): #Use 1D density, even though 2D
|
|||
raise Exception("Center must have 2 or 3 coordinates!")
|
||||
elif center.size == 2:
|
||||
center = np.append(center, [0])
|
||||
self.center = center
|
||||
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
|
||||
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)
|
||||
])
|
||||
|
@ -305,6 +318,18 @@ class Line(Mobject1D):
|
|||
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):
|
||||
|
|
112
moser/graphs.py
Normal file
112
moser/graphs.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
CUBE_GRAPH = {
|
||||
"name" : "CubeGraph",
|
||||
# 6 4
|
||||
# 20
|
||||
# 31
|
||||
# 7 5
|
||||
"vertices" : [
|
||||
(x, y, 0)
|
||||
for r in (1, 2)
|
||||
for x, y in it.product([-r,r], [-r, r])
|
||||
],
|
||||
"edges" : [
|
||||
(0, 1),
|
||||
(0, 2),
|
||||
(3, 1),
|
||||
(3, 2),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(7, 5),
|
||||
(7, 6),
|
||||
(0, 4),
|
||||
(1, 5),
|
||||
(2, 6),
|
||||
(3, 7),
|
||||
],
|
||||
"region_cycles" : [
|
||||
[0, 2, 3, 1],
|
||||
[4, 0, 1, 5],
|
||||
[4, 6, 2, 0],
|
||||
[6, 7, 3, 2],
|
||||
[7, 5, 1, 3],
|
||||
[4, 6, 7, 5],#By convention, last region will be "outside"
|
||||
]
|
||||
}
|
||||
|
||||
SAMPLE_GRAPH = {
|
||||
"name" : "SampleGraph",
|
||||
# 4 2 3
|
||||
# 0 1
|
||||
#
|
||||
# 5
|
||||
"vertices" :[
|
||||
( 0, 0, 0),
|
||||
( 2, 0, 0),
|
||||
( 1, 1, 0),
|
||||
( 3, 1, 0),
|
||||
(-1, 1, 0),
|
||||
(-2,-2, 0),
|
||||
],
|
||||
"edges" : [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(3, 2),
|
||||
(2, 4),
|
||||
(4, 0),
|
||||
(2, 0),
|
||||
(4, 5),
|
||||
(0, 5),
|
||||
(1, 5),
|
||||
],
|
||||
"region_cycles" : [
|
||||
(0, 1, 2),
|
||||
(1, 3, 2),
|
||||
(2, 4, 0),
|
||||
(4, 5, 0),
|
||||
(0, 5, 1),
|
||||
(4, 5, 1, 3),
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
OCTOHEDRON_GRAPH = {
|
||||
"name" : "OctohedronGraph",
|
||||
# 3
|
||||
#
|
||||
# 1 0
|
||||
# 2
|
||||
#4 5
|
||||
"vertices" : [
|
||||
(r*np.cos(angle), r*np.sin(angle)-1, 0)
|
||||
for r, s in [(1, 0), (3, 3)]
|
||||
for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s])
|
||||
],
|
||||
"edges" : [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(2, 0),
|
||||
(5, 0),
|
||||
(0, 3),
|
||||
(3, 5),
|
||||
(3, 1),
|
||||
(3, 4),
|
||||
(1, 4),
|
||||
(4, 2),
|
||||
(4, 5),
|
||||
(5, 2),
|
||||
],
|
||||
"region_cycles" : [
|
||||
(0, 1, 2),
|
||||
(0, 5, 3),
|
||||
(3, 1, 0),
|
||||
(3, 4, 1),
|
||||
(1, 4, 2),
|
||||
(2, 4, 5),
|
||||
(5, 0, 2),
|
||||
(3, 4, 5),
|
||||
]
|
||||
}
|
|
@ -28,7 +28,7 @@ def logo_to_circle():
|
|||
)
|
||||
big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS)
|
||||
sc.add(small_circle)
|
||||
sc.dither()
|
||||
sc.dither()`
|
||||
sc.animate(Transform(small_circle, big_circle))
|
||||
return sc
|
||||
|
||||
|
|
370
moser/main.py
370
moser/main.py
|
@ -4,6 +4,7 @@ import numpy as np
|
|||
import itertools as it
|
||||
import operator as op
|
||||
from copy import deepcopy
|
||||
from random import random
|
||||
|
||||
|
||||
from animation import *
|
||||
|
@ -14,10 +15,14 @@ from region import *
|
|||
from scene import Scene
|
||||
|
||||
from moser_helpers import *
|
||||
from graphs import *
|
||||
|
||||
RADIUS = SPACE_HEIGHT - 0.1
|
||||
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
||||
|
||||
movie_prefix = "moser/"
|
||||
|
||||
############################################
|
||||
|
||||
class CircleScene(Scene):
|
||||
def __init__(self, radians, *args, **kwargs):
|
||||
|
@ -32,6 +37,34 @@ class CircleScene(Scene):
|
|||
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
|
||||
|
||||
##################################################
|
||||
|
||||
|
@ -72,7 +105,7 @@ def count_lines(*radians):
|
|||
else:
|
||||
anims.append(FadeOut(mob))
|
||||
sc.animate(*anims, run_time = 1)
|
||||
return sc
|
||||
sc.write_to_movie(movie_prefix + "CountLines%dPoints"%len(radians))
|
||||
|
||||
|
||||
def count_intersection_points(*radians):
|
||||
|
@ -84,7 +117,7 @@ def count_intersection_points(*radians):
|
|||
for p in it.combinations(sc.points, 4)
|
||||
]
|
||||
intersection_dots = [Dot(point) for point in intersection_points]
|
||||
text_center = (sc.radius + 1, sc.radius -0.5, 0)
|
||||
text_center = (sc.radius + 0.5, sc.radius -0.5, 0)
|
||||
size = r"\large"
|
||||
scale_factor = 0.4
|
||||
text = tex_mobject(r"\text{How Many Intersection Points?}", size = size)
|
||||
|
@ -107,7 +140,7 @@ def count_intersection_points(*radians):
|
|||
# ])
|
||||
|
||||
sc.add(text)
|
||||
sc.count(intersection_dots, "show_creation", num_offset = (0, 0, 0))
|
||||
sc.count(intersection_dots, "show", num_offset = (0, 0, 0))
|
||||
sc.dither()
|
||||
# sc.animate(Transform(intersection_dots, new_dots))
|
||||
anims = []
|
||||
|
@ -116,9 +149,11 @@ def count_intersection_points(*radians):
|
|||
anims.append(Transform(mob, answer))
|
||||
else:
|
||||
anims.append(FadeOut(mob))
|
||||
anims.append(FadeIn(formula)) #Put here to so they are foreground
|
||||
anims.append(Animation(formula))
|
||||
sc.animate(*anims, run_time = 1)
|
||||
return sc
|
||||
|
||||
name = "CountIntersectionPoints%dPoints"%len(radians)
|
||||
sc.write_to_movie(movie_prefix + name)
|
||||
|
||||
def non_general_position():
|
||||
radians = np.arange(1, 7)
|
||||
|
@ -151,7 +186,7 @@ def non_general_position():
|
|||
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
|
||||
for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects)
|
||||
])
|
||||
return sc1
|
||||
sc1.write_to_movie(movie_prefix + "NonGeneralPosition")
|
||||
|
||||
def line_corresponds_with_pair(radians, r1, r2):
|
||||
sc = CircleScene(radians)
|
||||
|
@ -165,40 +200,319 @@ def line_corresponds_with_pair(radians, r1, r2):
|
|||
sc.dots.remove(dot0)
|
||||
sc.dots.remove(dot1)
|
||||
sc.dither()
|
||||
sc.animate(*[FadeOut(mob) for mob in sc.lines + sc.dots])
|
||||
sc.animate(*[
|
||||
FadeOut(mob, alpha_func = not_quite_there)
|
||||
for mob in sc.lines + sc.dots
|
||||
])
|
||||
sc.add(sc.circle)
|
||||
sc.animate(*[
|
||||
ScaleInPlace(mob, 3, alpha_func = there_and_back)
|
||||
for mob in (dot0, dot1)
|
||||
])
|
||||
sc.animate(Transform(line, dot0))
|
||||
return sc
|
||||
name = "LineCorrspondsWithPair%d%d"%(dot0_index, dot1_index)
|
||||
sc.write_to_movie(movie_prefix + name)
|
||||
|
||||
def illustrate_n_choose_2(n):
|
||||
#TODO, maybe make this snazzy
|
||||
def illustrate_n_choose_k(n, k):
|
||||
sc = Scene()
|
||||
nrange = range(1, n+1)
|
||||
nrange_im = tex_mobject(str(nrange))
|
||||
pairs_str = str(list(it.combinations(nrange, 2)))
|
||||
exp = tex_mobject(r"{{%d \choose 2} = %d \text{ total pairs}}"%(n, choose(n, 2)))
|
||||
pairs_im = tex_mobject(r"\underbrace{%s}"%pairs_str, size=r"\tiny")
|
||||
nrange_im.shift((0, 2, 0))
|
||||
pairs_im.scale(0.7)
|
||||
exp.shift((0, -2, 0))
|
||||
sc.add(nrange_im)
|
||||
tuples = list(it.combinations(nrange, k))
|
||||
nrange_mobs = tex_mobjects([str(n) + r'\;' for n in nrange])
|
||||
tuple_mobs = tex_mobjects(
|
||||
[
|
||||
(r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p)
|
||||
for p, c in zip(tuples, it.count())
|
||||
],
|
||||
size = r"\small"
|
||||
)
|
||||
tuple_terms = {
|
||||
2 : "pairs",
|
||||
3 : "triplets",
|
||||
4 : "quadruplets",
|
||||
}
|
||||
tuple_term = tuple_terms[k] if k in tuple_terms else "tuples"
|
||||
form1, count, form2 = tex_mobject([
|
||||
r"{%d \choose %d} = "%(n, k),
|
||||
"%d"%choose(n, k),
|
||||
r" \text{ total %s}"%tuple_term
|
||||
])
|
||||
for mob in nrange_mobs:
|
||||
mob.shift((0, 2, 0))
|
||||
for mob in form1, count, form2:
|
||||
mob.shift((0, -SPACE_HEIGHT + 1, 0))
|
||||
count_center = count.get_center()
|
||||
for mob in tuple_mobs:
|
||||
mob.scale(0.6)
|
||||
|
||||
sc.add(*nrange_mobs)
|
||||
sc.dither()
|
||||
sc.animate(FadeIn(pairs_im), FadeIn(exp))
|
||||
sc.add(pairs_im)
|
||||
return sc
|
||||
|
||||
run_time = 6.0
|
||||
frame_time = run_time / len(tuples)
|
||||
for tup, count in zip(tuples, it.count()):
|
||||
count_mob = tex_mobject(str(count+1))
|
||||
count_mob.center().shift(count_center)
|
||||
sc.add(count_mob)
|
||||
tuple_copy = CompoundMobject(*[nrange_mobs[index-1] for index in tup])
|
||||
tuple_copy.highlight()
|
||||
sc.add(tuple_copy)
|
||||
sc.add(tuple_mobs[count])
|
||||
sc.dither(frame_time)
|
||||
sc.remove(count_mob)
|
||||
sc.remove(tuple_copy)
|
||||
sc.add(count_mob)
|
||||
sc.animate(FadeIn(CompoundMobject(form1, form2)))
|
||||
sc.write_to_movie(movie_prefix + "Illustrate%dChoose%d"%(n, k))
|
||||
|
||||
def intersection_point_correspondances(radians, indices):
|
||||
assert(len(indices) == 4)
|
||||
indices.sort()
|
||||
sc = CircleScene(radians)
|
||||
intersection_point = intersection(
|
||||
(sc.points[indices[0]], sc.points[indices[2]]),
|
||||
(sc.points[indices[1]], sc.points[indices[3]])
|
||||
)
|
||||
intersection_point = tuple(list(intersection_point) + [0])
|
||||
intersection_dot = Dot(intersection_point)
|
||||
intersection_dot_arrow = Arrow(intersection_point).nudge()
|
||||
sc.add(intersection_dot)
|
||||
pairs = list(it.combinations(range(len(radians)), 2))
|
||||
lines_to_save = [
|
||||
sc.lines[pairs.index((indices[p0], indices[p1]))]
|
||||
for p0, p1 in [(0, 2), (1, 3)]
|
||||
]
|
||||
dots_to_save = [
|
||||
sc.dots[p]
|
||||
for p in indices
|
||||
]
|
||||
line_statement = tex_mobject(r"\text{Pair of Lines}")
|
||||
dots_statement = tex_mobject(r"&\text{Quadruplet of} \\ &\text{outer dots}")
|
||||
for mob in line_statement, dots_statement:
|
||||
mob.center()
|
||||
mob.scale(0.7)
|
||||
mob.shift((SPACE_WIDTH-2, SPACE_HEIGHT - 1, 0))
|
||||
fade_outs = []
|
||||
line_highlights = []
|
||||
dot_highlights = []
|
||||
dot_pointers = []
|
||||
for mob in sc.mobjects:
|
||||
if mob in lines_to_save:
|
||||
line_highlights.append(Highlight(mob))
|
||||
elif mob in dots_to_save:
|
||||
dot_highlights.append(Highlight(mob))
|
||||
dot_pointers.append(Arrow(mob.get_center()).nudge())
|
||||
elif mob != intersection_dot:
|
||||
fade_outs.append(FadeOut(mob, alpha_func = not_quite_there))
|
||||
|
||||
sc.add(intersection_dot_arrow)
|
||||
sc.animate(Highlight(intersection_dot))
|
||||
sc.remove(intersection_dot_arrow)
|
||||
sc.animate(*fade_outs)
|
||||
sc.dither()
|
||||
sc.add(line_statement)
|
||||
sc.animate(*line_highlights)
|
||||
sc.remove(line_statement)
|
||||
sc.dither()
|
||||
sc.add(dots_statement, *dot_pointers)
|
||||
sc.animate(*dot_highlights)
|
||||
sc.remove(dots_statement, *dot_pointers)
|
||||
|
||||
name = "IntersectionPointCorrespondances"
|
||||
for ind in indices:
|
||||
name += str(ind)
|
||||
sc.write_to_movie(movie_prefix + name)
|
||||
|
||||
def lines_intersect_outside(radians, indices):
|
||||
assert(len(indices) == 4)
|
||||
indices.sort()
|
||||
sc = CircleScene(radians)
|
||||
intersection_point = intersection(
|
||||
(sc.points[indices[0]], sc.points[indices[1]]),
|
||||
(sc.points[indices[2]], sc.points[indices[3]])
|
||||
)
|
||||
intersection_point = tuple(list(intersection_point) + [0])
|
||||
intersection_dot = Dot(intersection_point)
|
||||
pairs = list(it.combinations(range(len(radians)), 2))
|
||||
lines_to_save = [
|
||||
sc.lines[pairs.index((indices[p0], indices[p1]))]
|
||||
for p0, p1 in [(0, 1), (2, 3)]
|
||||
]
|
||||
sc.animate(*[
|
||||
FadeOut(mob, alpha_func = not_quite_there)
|
||||
for mob in sc.mobjects if mob not in lines_to_save
|
||||
])
|
||||
sc.animate(*[
|
||||
Transform(
|
||||
Line(sc.points[indices[p0]], sc.points[indices[p1]]),
|
||||
Line(sc.points[indices[p0]], intersection_point))
|
||||
for p0, p1 in [(0, 1), (3, 2)]
|
||||
] + [ShowCreation(intersection_dot)])
|
||||
|
||||
name = "LinesIntersectOutside"
|
||||
for ind in indices:
|
||||
name += str(ind)
|
||||
sc.write_to_movie(movie_prefix + name)
|
||||
|
||||
def quadruplets_to_intersections(*radians):
|
||||
sc = CircleScene(radians)
|
||||
quadruplets = it.combinations(range(len(radians)), 4)
|
||||
frame_time = 1.0
|
||||
for quad in quadruplets:
|
||||
intersection_dot = Dot(intersection(
|
||||
(sc.points[quad[0]], sc.points[quad[2]]),
|
||||
(sc.points[quad[1]], sc.points[quad[3]])
|
||||
)).repeat(3)
|
||||
dot_quad = [deepcopy(sc.dots[i]) for i in quad]
|
||||
for dot in dot_quad:
|
||||
dot.scale_in_place(2)
|
||||
# arrows = [Arrow(d.get_center()) for d in dot_quad]
|
||||
dot_quad = CompoundMobject(*dot_quad)
|
||||
# arrows = CompoundMobject(*arrows)
|
||||
dot_quad.highlight()
|
||||
# sc.add(arrows)
|
||||
sc.add(dot_quad)
|
||||
sc.dither(frame_time / 3)
|
||||
sc.animate(Transform(
|
||||
dot_quad,
|
||||
intersection_dot,
|
||||
run_time = 3*frame_time/2
|
||||
))
|
||||
# sc.remove(arrows)
|
||||
|
||||
name = "QuadrupletsToIntersections" + len(radians)
|
||||
sc.write_to_movie(movie_prefix + name)
|
||||
|
||||
def defining_graph(graph):
|
||||
gs = GraphScene(graph)
|
||||
dots, lines = gs.vertices, gs.edges
|
||||
gs.remove(*dots + lines)
|
||||
all_dots = CompoundMobject(*dots)
|
||||
gs.animate(ShowCreation(all_dots))
|
||||
gs.remove(all_dots)
|
||||
gs.add(*dots)
|
||||
gs.dither()
|
||||
gs.animate(*[
|
||||
ShowCreation(line) for line in lines
|
||||
])
|
||||
|
||||
#Move to new graph
|
||||
new_graph = deepcopy(graph)
|
||||
new_graph["vertices"] = [
|
||||
(v[0] + 3*random(), v[1] + 3*random(), 0)
|
||||
for v in new_graph["vertices"]
|
||||
]
|
||||
ngs = GraphScene(new_graph)
|
||||
gs.animate(*[
|
||||
Transform(m[0], m[1])
|
||||
for m in zip(gs.mobjects, ngs.mobjects)
|
||||
], run_time = 7.0)
|
||||
|
||||
name = "DefiningGraph" + graph["name"]
|
||||
gs.write_to_movie(movie_prefix + name)
|
||||
|
||||
def doubled_edges(graph):
|
||||
gs = GraphScene(graph)
|
||||
lines_to_double = gs.edges[:9:3]
|
||||
crazy_lines = [
|
||||
(
|
||||
line,
|
||||
Line(line.end, line.start),
|
||||
CurvedLine(line.start, line.end) ,
|
||||
CurvedLine(line.end, line.start)
|
||||
)
|
||||
for line in lines_to_double
|
||||
]
|
||||
anims = []
|
||||
outward_curved_lines = []
|
||||
kwargs = {"run_time" : 3.0}
|
||||
for straight, backwards, inward, outward in crazy_lines:
|
||||
anims += [
|
||||
Transform(straight, inward, **kwargs),
|
||||
Transform(backwards, outward, **kwargs),
|
||||
]
|
||||
outward_curved_lines.append(outward)
|
||||
gs.animate(*anims)
|
||||
gs.dither()
|
||||
gs.remove(*outward_curved_lines)
|
||||
|
||||
name = "DoubledEdges" + graph["name"]
|
||||
gs.write_to_movie(movie_prefix + name)
|
||||
|
||||
|
||||
def eulers_formula(graph):
|
||||
gs = GraphScene(graph)
|
||||
terms = "V - E + F =2".split(" ")
|
||||
form = dict([
|
||||
(key, mob)
|
||||
for key, mob in zip(terms, tex_mobjects(terms))
|
||||
])
|
||||
for mob in form.values():
|
||||
mob.shift((0, SPACE_HEIGHT-1.5, 0))
|
||||
formula = CompoundMobject(*form.values())
|
||||
new_form = dict([
|
||||
(key, deepcopy(mob).shift((0, -0.7, 0)))
|
||||
for key, mob in zip(form.keys(), form.values())
|
||||
])
|
||||
gs.add(formula)
|
||||
colored_dots = [
|
||||
deepcopy(d).scale_in_place(1.5).highlight("red")
|
||||
for d in gs.dots
|
||||
]
|
||||
colored_edges = [
|
||||
deepcopy(e).highlight("red")
|
||||
for e in gs.edges
|
||||
]
|
||||
frame_time = 0.3
|
||||
|
||||
gs.generate_regions()
|
||||
parameters = [
|
||||
(colored_dots, "V", "mobject", "-", "show_creation"),
|
||||
(colored_edges, "E", "mobject", "+", "show_creation"),
|
||||
(gs.regions, "F", "region", "=2", "show_all")
|
||||
]
|
||||
for items, letter, item_type, symbol, mode in parameters:
|
||||
gs.count(
|
||||
items,
|
||||
item_type = item_type,
|
||||
mode = mode,
|
||||
num_offset = new_form[letter].get_center(),
|
||||
run_time = frame_time*len(items)
|
||||
)
|
||||
gs.dither()
|
||||
if item_type == "mobject":
|
||||
gs.remove(*items)
|
||||
gs.add(new_form[symbol])
|
||||
gs.reset_background()
|
||||
|
||||
name = "EulersFormula" + graph["name"]
|
||||
gs.write_to_movie(movie_prefix + name)
|
||||
|
||||
|
||||
##################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
radians = np.arange(0, 6, 6.0/7)
|
||||
# count_lines(*radians).write_to_movie("moser/CountLines")
|
||||
count_intersection_points(*radians).write_to_movie("moser/CountIntersectionPoints")
|
||||
# non_general_position().write_to_movie("moser/NonGeneralPosition")
|
||||
# line_corresponds_with_pair(radians, radians[3], radians[4]).write_to_movie("moser/LineCorrespondsWithPair34")
|
||||
# line_corresponds_with_pair(radians, radians[2], radians[5]).write_to_movie("moser/LineCorrespondsWithPair25")
|
||||
# illustrate_n_choose_2(6).write_to_movie("moser/IllustrateNChoose2with6")
|
||||
# count_lines(*radians)
|
||||
# count_lines(*radians[:4])
|
||||
# count_intersection_points(*radians[:4])
|
||||
# count_intersection_points(*radians[:6])
|
||||
# count_intersection_points(*radians)
|
||||
# non_general_position()
|
||||
# line_corresponds_with_pair(radians, radians[3], radians[4])
|
||||
# line_corresponds_with_pair(radians, radians[2], radians[5])
|
||||
# illustrate_n_choose_k(7, 2)
|
||||
# illustrate_n_choose_k(6, 4)
|
||||
# intersection_point_correspondances(radians, range(0, 7, 2))
|
||||
# lines_intersect_outside(radians, [2, 4, 5, 6])
|
||||
quadruplets_to_intersections(*radians[:6])
|
||||
# defining_graph(SAMPLE_GRAPH)
|
||||
# doubled_edges(CUBE_GRAPH)
|
||||
# eulers_formula(CUBE_GRAPH)
|
||||
# eulers_formula(SAMPLE_GRAPH)
|
||||
# eulers_formula(OCTOHEDRON_GRAPH)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -76,6 +76,12 @@ class HalfPlane(Region):
|
|||
return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0)
|
||||
Region.__init__(self, condition, *args, **kwargs)
|
||||
|
||||
def region_from_line_boundary(*lines):
|
||||
reg = Region()
|
||||
for line in lines:
|
||||
reg.intersect(HalfPlane(line))
|
||||
return reg
|
||||
|
||||
def plane_partition(*lines):
|
||||
"""
|
||||
A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)]
|
||||
|
|
53
scene.py
53
scene.py
|
@ -16,6 +16,9 @@ from image_mobject import *
|
|||
from animation import *
|
||||
import displayer as disp
|
||||
|
||||
DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
|
||||
DEFAULT_COUNT_RUN_TIME = 5.0
|
||||
|
||||
class Scene(object):
|
||||
def __init__(self,
|
||||
name = None,
|
||||
|
@ -99,19 +102,29 @@ class Scene(object):
|
|||
self.add(*moving_mobjects)
|
||||
progress_bar.finish()
|
||||
|
||||
def count(self, mobjects, mode = "highlight",
|
||||
color = "red",
|
||||
num_offset = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0),
|
||||
run_time = 5.0):
|
||||
def count(self, items, item_type = "mobject", *args, **kwargs):
|
||||
if item_type == "mobject":
|
||||
self.count_mobjects(items, *args, **kwargs)
|
||||
elif item_type == "region":
|
||||
self.count_regions(items, *args, **kwargs)
|
||||
|
||||
def count_mobjects(
|
||||
self, mobjects, mode = "highlight",
|
||||
color = "red",
|
||||
num_offset = DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time = DEFAULT_COUNT_RUN_TIME):
|
||||
"""
|
||||
Note: Leaves scene with a "number" attribute
|
||||
for the final number mobject
|
||||
for the final number mobject.
|
||||
|
||||
mode can be "highlight", "show_creation" or "show", otherwise
|
||||
a warning is given and nothing is animating during the count
|
||||
"""
|
||||
if len(mobjects) > 50: #TODO
|
||||
raise Exception("I don't know if you should be counting \
|
||||
too many mobjects...")
|
||||
if mode not in ["highlight", "show_creation"]:
|
||||
raise Exception("Invalid mode")
|
||||
if mode not in ["highlight", "show_creation", "show"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / len(mobjects)
|
||||
if mode == "highlight":
|
||||
self.add(*mobjects)
|
||||
|
@ -126,10 +139,34 @@ class Scene(object):
|
|||
mob.highlight(original_color)
|
||||
if mode == "show_creation":
|
||||
self.animate(ShowCreation(mob, run_time = frame_time))
|
||||
if mode == "show":
|
||||
self.add(mob)
|
||||
self.dither(frame_time)
|
||||
self.remove(num_mob)
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
|
||||
def count_regions(self, regions,
|
||||
mode = "one_at_a_time",
|
||||
num_offset = DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time = DEFAULT_COUNT_RUN_TIME,
|
||||
**unused_kwargsn):
|
||||
if mode not in ["one_at_a_time", "show_all"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / (len(regions))
|
||||
for region, count in zip(regions, it.count(1)):
|
||||
num_mob = tex_mobject(str(count))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
self.highlight_region(region)
|
||||
self.dither(frame_time)
|
||||
if mode == "one_at_a_time":
|
||||
self.reset_background()
|
||||
self.remove(num_mob)
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
|
||||
|
||||
def get_frame(self):
|
||||
frame = self.background
|
||||
for mob in self.mobjects:
|
||||
|
@ -147,7 +184,7 @@ class Scene(object):
|
|||
self.dither(end_dither_time)
|
||||
disp.write_to_movie(self, name or str(self))
|
||||
|
||||
def show_frame(self):
|
||||
def show(self):
|
||||
Image.fromarray(self.get_frame()).show()
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue