starting main moser video

This commit is contained in:
Grant Sanderson 2015-04-14 17:55:25 -07:00
parent 398b92406c
commit d79db9d142
13 changed files with 225 additions and 46 deletions

BIN
Images/simple_face.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
Images/speech_bubble.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
Images/thought_bubble.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
Images/video_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -10,6 +10,7 @@ import inspect
from images2gif import writeGif
from helpers import *
from constants import *
from mobject import Mobject
class Animation(object):

View file

@ -1,15 +1,15 @@
import os
PRODUCTION_QUALITY = True
GENERALLY_BUFF_POINTS = False
GENERALLY_BUFF_POINTS = True
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
DEFAULT_POINT_DENSITY_1D = 200 #if PRODUCTION_QUALITY else 50
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50
DEFAULT_HEIGHT = 1440 if PRODUCTION_QUALITY else 480
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 640
#All in seconds
DEFAULT_FRAME_DURATION = 0.04 #if PRODUCTION_QUALITY else 0.1
DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1
DEFAULT_ANIMATION_RUN_TIME = 3.0
DEFAULT_TRANSFORM_RUN_TIME = 1.0
DEFAULT_DITHER_TIME = 1.0

View file

@ -5,6 +5,8 @@ from animation import *
from mobject import *
from constants import *
from helpers import *
from scene import *
from image_mobject import *
import itertools as it
import os
@ -14,12 +16,12 @@ import numpy as np
DARK_BLUE = "#236B8E"
DARK_BROWN = "#8B4513"
LIGHT_BROWN = "#CD853F"
LOGO_RADIUS = 1.5
if __name__ == '__main__':
size = 1.5
circle = Circle(color = 'skyblue').repeat(4).scale(size)
sphere = Sphere(density = 100, color = DARK_BLUE).scale(size)
circle = Circle(density = 100, color = 'skyblue').repeat(5).scale(LOGO_RADIUS)
sphere = Sphere(density = 100, color = DARK_BLUE).scale(LOGO_RADIUS)
sphere.rotate(-np.pi / 7, [1, 0, 0])
sphere.rotate(-np.pi / 7)
alpha = 0.3
@ -27,20 +29,21 @@ if __name__ == '__main__':
Mobject.interpolate(circle, sphere, iris, alpha)
for mob, color in [(iris, LIGHT_BROWN), (circle, DARK_BROWN)]:
mob.highlight(color, lambda (x, y, z) : x < 0 and y > 0)
mob.highlight("black", lambda point: np.linalg.norm(point) < size/2)
mob.highlight("black", lambda point: np.linalg.norm(point) < 0.55*LOGO_RADIUS)
#TODO, reimplement all of this.
name = tex_mobject("3Blue1Brown").center()
name = tex_mobject(r"\text{3Blue1Brown}").center()
name.highlight("gray")
# name.highlight(DARK_BROWN, lambda (x, y, z) : x < 0 and y > 0)
name.shift((0, -2, 0))
create_eye = Transform(
sc = Scene()
sc.animate(Transform(
circle, iris,
run_time = DEFAULT_ANIMATION_RUN_TIME,
name = "LogoGeneration"
).then(
Animation(name, dither_time = 0)
).drag_pixels()
create_eye.write_to_movie()
index = int(DEFAULT_ANIMATION_RUN_TIME / DEFAULT_ANIMATION_PAUSE_TIME)
create_eye.frames[index].save(LOGO_PATH)
run_time = DEFAULT_ANIMATION_RUN_TIME
))
sc.add(name)
sc.dither()
sc.frames = drag_pixels(sc.frames)
sc.write_to_movie("LogoGeneration", end_dither_time = 0)
# index = int(DEFAULT_ANIMATION_RUN_TIME / DEFAULT_ANIMATION_PAUSE_TIME)
# create_eye.frames[index].save(LOGO_PATH)

View file

@ -4,6 +4,7 @@ from PIL import Image
from colour import Color
from random import random
import string
from constants import *
def hash_args(args):
@ -25,13 +26,13 @@ def to_cammel_case(name):
]
return "".join(parts)
def drag_pixels(images):
curr = np.array(images[0])
new_images = []
for image in images:
curr += (curr == 0) * np.array(image)
new_images.append(Image.fromarray(curr))
return new_images
def drag_pixels(frames):
curr = frames[0]
new_frames = []
for frame in frames:
curr += (curr == 0) * np.array(frame)
new_frames.append(np.array(curr))
return new_frames
def invert_image(image):
arr = np.array(image)

View file

@ -4,6 +4,7 @@ import os
from PIL import Image
from random import random
from copy import deepcopy
from colour import Color
from constants import *
from helpers import *
@ -264,6 +265,11 @@ class Vector(Arrow):
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 = center
self.radius = radius
Mobject1D.__init__(self, *args, **kwargs)

View file

@ -17,27 +17,15 @@ RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
####
def moser_function(n):
return choose(n, 4) + choose(n, 2) + 1
def choose(n, r):
if n < r: return 0
r = min(r, n-r)
if r == 0: return 1
numer = reduce(op.mul, xrange(n, n-r, -1), 1)
denom = reduce(op.mul, xrange(1, r+1), 1)
return numer//denom
####
def logo_to_circle():
from generate_logo import LIGHT_BROWN
from generate_logo import DARK_BROWN, LOGO_RADIUS
sc = Scene()
small_circle = Circle(
density = CIRCLE_DENSITY,
color = 'skyblue'
).highlight(LIGHT_BROWN, lambda (x, y, z) : x < 0 and y > 0)
).scale(LOGO_RADIUS).highlight(
DARK_BROWN, lambda (x, y, z) : x < 0 and y > 0
)
big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS)
sc.add(small_circle)
sc.dither()
@ -102,7 +90,7 @@ def summarize_pattern(*radians):
new_lines = CompoundMobject(*[
Line(points[x], points[y]) for y in xrange(x)
])
num = tex_mobject(str(moser_function(x + 1)))
num = tex_mobject(str(moser_function(x + 1))).center()
sc.animate(
Transform(last_num, num) if last_num else ShowCreation(num),
FadeIn(new_lines),
@ -270,6 +258,7 @@ def next_few_videos(*radians):
return sc
if __name__ == '__main__':
radians = [1, 3, 5, 2, 4, 6]
more_radians = radians + [10, 13, 20, 17, 15, 21, 18.5]
@ -282,13 +271,20 @@ if __name__ == '__main__':
# connect_points(*radians).write_to_movie("moser/ConnectPoints")
# response_invitation().write_to_movie("moser/ResponseInvitation")
# different_points(radians, different_radians).write_to_movie("moser/DifferentPoints")
next_few_videos(*radians).write_to_movie("moser/NextFewVideos")
# next_few_videos(*radians).write_to_movie("moser/NextFewVideos")
# summarize_pattern(*different_radians).write_to_movie("moser/PatternWithDifferentPoints")
#Images
# tex_mobject(r"""
# \underbrace{1, 2, 4, 8, 16, 31, \dots}_\text{What?}
# """).save_image("moser/NumberList")
# """).save_image("moser/NumberList31")
# tex_mobject("""
# 1, 2, 4, 8, 16, 32, 63, \dots
# """).save_image("moser/NumberList63")
# tex_mobject("""
# 1, 2, 4, 8, 15, \dots
# """).save_image("moser/NumberList15")

137
moser/main.py Normal file
View file

@ -0,0 +1,137 @@
#!/usr/bin/env python
import numpy as np
import itertools as it
import operator as op
from copy import deepcopy
from animation import *
from mobject import *
from image_mobject import *
from constants import *
from region import *
from scene import Scene
from moser_helpers import *
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.add(self.circle, *self.dots + self.lines)
##################################################
def count_lines(*radians):
sc = CircleScene(radians)
text = tex_mobject(r"\text{How Many Lines?}", size = r"\large")
text_center = (sc.radius + 1, sc.radius -1, 0)
text.scale(0.4).shift(text_center)
x = text_center[0]
new_lines = [
Line((x-1, y, 0), (x+1, y, 0))
for y in np.arange(
-(sc.radius - 1),
sc.radius - 1,
(2*sc.radius - 2)/len(sc.lines)
)
]
sc.add(text)
sc.dither()
sc.animate(*[
Transform(line1, line2, run_time = 2)
for line1, line2 in zip(sc.lines, new_lines)
])
return sc
def count_intersection_points(*radians):
radians = [r % (2*np.pi) for r in radians]
radians.sort()
sc = CircleScene(radians)
intersection_points = [
intersection([p[0], p[2]], [p[1], p[3]])
for p in it.combinations(sc.points, 4)
]
intersection_dots = CompoundMobject(*[
Dot(point) for point in intersection_points
])
how_many = tex_mobject(r"""
\text{How many}\\
\text{intersection points?}
""", size = r"\large")
text_center = (sc.radius + 1, sc.radius -1, 0)
how_many.scale(0.4).shift(text_center)
new_points = [
(text_center[0], y, 0)
for y in np.arange(
-(sc.radius - 1),
sc.radius - 1,
(2*sc.radius - 2)/choose(len(sc.points), 4)
)
]
new_dots = CompoundMobject(*[
Dot(point) for point in new_points
])
sc.add(how_many)
sc.animate(ShowCreation(intersection_dots))
sc.add(intersection_dots)
sc.animate(Transform(intersection_dots, new_dots))
sc.add(tex_mobject(str(len(new_points))).center())
return sc
def non_general_position():
radians = np.arange(1, 7)
new_radians = (np.pi/3)*radians
sc1 = CircleScene(radians)
sc2 = CircleScene(new_radians)
center_region = reduce(
Region.intersect,
[
HalfPlane((sc1.points[x], sc1.points[(x+3)%6]))
for x in [0, 4, 2]#Ya know, trust it
]
)
center_region
text = tex_mobject(r"\text{This region disappears}", size = r"\large")
text.center().scale(0.5).shift((-sc1.radius, sc1.radius-0.3, 0))
arrow = Arrow(
point = (-0.35, -0.1, 0),
direction = (1, -1, 0),
length = sc1.radius + 1,
color = "white",
)
sc1.highlight_region(center_region, "green")
sc1.add(text, arrow)
sc1.dither(2)
sc1.remove(text, arrow)
sc1.reset_background()
sc1.animate(*[
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects)
])
return sc1
##################################################
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")

34
moser/moser_helpers.py Normal file
View file

@ -0,0 +1,34 @@
import numpy as np
import operator as op
import itertools as it
def choose(n, r):
if n < r: return 0
if r == 0: return 1
denom = reduce(op.mul, xrange(1, r+1), 1)
numer = reduce(op.mul, xrange(n, n-r, -1), 1)
return numer//denom
def moser_function(n):
return choose(n, 4) + choose(n, 2) + 1
def intersection(line1, line2):
"""
A "line" should come in the form [(x0, y0), (x1, y1)] for two
points it runs through
"""
p0, p1, p2, p3 = map(
lambda tup : np.array(tup[:2]),
[line1[0], line1[1], line2[0], line2[1]]
)
p1, p2, p3 = map(lambda x : x - p0, [p1, p2, p3])
transform = np.zeros((2, 2))
transform[:,0], transform[:,1] = p1, p2
if np.linalg.det(transform) == 0: return
inv = np.linalg.inv(transform)
new_p3 = np.dot(inv, p3.reshape((2, 1)))
#Where does line connecting (0, 1) to new_p3 hit x axis
x_intercept = new_p3[0] / (1 - new_p3[1])
result = np.dot(transform, [[x_intercept], [0]])
result = result.reshape((2,)) + p0
return result

View file

@ -36,6 +36,7 @@ class Scene(object):
)
self.background = self.original_background
self.shape = self.background.shape[:2]
#TODO, space shape
self.name = name
def __str__(self):