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 images2gif import writeGif
from helpers import * from helpers import *
from constants import *
from mobject import Mobject from mobject import Mobject
class Animation(object): class Animation(object):

View file

@ -1,15 +1,15 @@
import os import os
PRODUCTION_QUALITY = True PRODUCTION_QUALITY = True
GENERALLY_BUFF_POINTS = False GENERALLY_BUFF_POINTS = True
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20 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_HEIGHT = 1440 if PRODUCTION_QUALITY else 480
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 640 DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 640
#All in seconds #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_ANIMATION_RUN_TIME = 3.0
DEFAULT_TRANSFORM_RUN_TIME = 1.0 DEFAULT_TRANSFORM_RUN_TIME = 1.0
DEFAULT_DITHER_TIME = 1.0 DEFAULT_DITHER_TIME = 1.0

View file

@ -5,6 +5,8 @@ from animation import *
from mobject import * from mobject import *
from constants import * from constants import *
from helpers import * from helpers import *
from scene import *
from image_mobject import *
import itertools as it import itertools as it
import os import os
@ -14,12 +16,12 @@ import numpy as np
DARK_BLUE = "#236B8E" DARK_BLUE = "#236B8E"
DARK_BROWN = "#8B4513" DARK_BROWN = "#8B4513"
LIGHT_BROWN = "#CD853F" LIGHT_BROWN = "#CD853F"
LOGO_RADIUS = 1.5
if __name__ == '__main__': if __name__ == '__main__':
size = 1.5 circle = Circle(density = 100, color = 'skyblue').repeat(5).scale(LOGO_RADIUS)
circle = Circle(color = 'skyblue').repeat(4).scale(size) sphere = Sphere(density = 100, color = DARK_BLUE).scale(LOGO_RADIUS)
sphere = Sphere(density = 100, color = DARK_BLUE).scale(size)
sphere.rotate(-np.pi / 7, [1, 0, 0]) sphere.rotate(-np.pi / 7, [1, 0, 0])
sphere.rotate(-np.pi / 7) sphere.rotate(-np.pi / 7)
alpha = 0.3 alpha = 0.3
@ -27,20 +29,21 @@ if __name__ == '__main__':
Mobject.interpolate(circle, sphere, iris, alpha) Mobject.interpolate(circle, sphere, iris, alpha)
for mob, color in [(iris, LIGHT_BROWN), (circle, DARK_BROWN)]: for mob, color in [(iris, LIGHT_BROWN), (circle, DARK_BROWN)]:
mob.highlight(color, lambda (x, y, z) : x < 0 and y > 0) 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(r"\text{3Blue1Brown}").center()
name = tex_mobject("3Blue1Brown").center()
name.highlight("gray") name.highlight("gray")
# name.highlight(DARK_BROWN, lambda (x, y, z) : x < 0 and y > 0)
name.shift((0, -2, 0)) name.shift((0, -2, 0))
create_eye = Transform( sc = Scene()
sc.animate(Transform(
circle, iris, circle, iris,
run_time = DEFAULT_ANIMATION_RUN_TIME, run_time = DEFAULT_ANIMATION_RUN_TIME
name = "LogoGeneration" ))
).then( sc.add(name)
Animation(name, dither_time = 0) sc.dither()
).drag_pixels() sc.frames = drag_pixels(sc.frames)
create_eye.write_to_movie() 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)
# 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 colour import Color
from random import random from random import random
import string import string
from constants import * from constants import *
def hash_args(args): def hash_args(args):
@ -25,13 +26,13 @@ def to_cammel_case(name):
] ]
return "".join(parts) return "".join(parts)
def drag_pixels(images): def drag_pixels(frames):
curr = np.array(images[0]) curr = frames[0]
new_images = [] new_frames = []
for image in images: for frame in frames:
curr += (curr == 0) * np.array(image) curr += (curr == 0) * np.array(frame)
new_images.append(Image.fromarray(curr)) new_frames.append(np.array(curr))
return new_images return new_frames
def invert_image(image): def invert_image(image):
arr = np.array(image) arr = np.array(image)

View file

@ -4,6 +4,7 @@ import os
from PIL import Image from PIL import Image
from random import random from random import random
from copy import deepcopy from copy import deepcopy
from colour import Color
from constants import * from constants import *
from helpers import * from helpers import *
@ -264,6 +265,11 @@ class Vector(Arrow):
class Dot(Mobject1D): #Use 1D density, even though 2D class Dot(Mobject1D): #Use 1D density, even though 2D
DEFAULT_COLOR = "white" DEFAULT_COLOR = "white"
def __init__(self, center = (0, 0, 0), radius = 0.05, *args, **kwargs): 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.center = center
self.radius = radius self.radius = radius
Mobject1D.__init__(self, *args, **kwargs) Mobject1D.__init__(self, *args, **kwargs)

View file

@ -17,27 +17,15 @@ RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS 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(): def logo_to_circle():
from generate_logo import LIGHT_BROWN from generate_logo import DARK_BROWN, LOGO_RADIUS
sc = Scene() sc = Scene()
small_circle = Circle( small_circle = Circle(
density = CIRCLE_DENSITY, density = CIRCLE_DENSITY,
color = 'skyblue' 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) big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS)
sc.add(small_circle) sc.add(small_circle)
sc.dither() sc.dither()
@ -102,7 +90,7 @@ def summarize_pattern(*radians):
new_lines = CompoundMobject(*[ new_lines = CompoundMobject(*[
Line(points[x], points[y]) for y in xrange(x) 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( sc.animate(
Transform(last_num, num) if last_num else ShowCreation(num), Transform(last_num, num) if last_num else ShowCreation(num),
FadeIn(new_lines), FadeIn(new_lines),
@ -270,6 +258,7 @@ def next_few_videos(*radians):
return sc return sc
if __name__ == '__main__': if __name__ == '__main__':
radians = [1, 3, 5, 2, 4, 6] radians = [1, 3, 5, 2, 4, 6]
more_radians = radians + [10, 13, 20, 17, 15, 21, 18.5] 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") # connect_points(*radians).write_to_movie("moser/ConnectPoints")
# response_invitation().write_to_movie("moser/ResponseInvitation") # response_invitation().write_to_movie("moser/ResponseInvitation")
# different_points(radians, different_radians).write_to_movie("moser/DifferentPoints") # 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 #Images
# tex_mobject(r""" # tex_mobject(r"""
# \underbrace{1, 2, 4, 8, 16, 31, \dots}_\text{What?} # \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.background = self.original_background
self.shape = self.background.shape[:2] self.shape = self.background.shape[:2]
#TODO, space shape
self.name = name self.name = name
def __str__(self): def __str__(self):