mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
starting main moser video
This commit is contained in:
parent
398b92406c
commit
d79db9d142
13 changed files with 225 additions and 46 deletions
BIN
Images/simple_face.png
Normal file
BIN
Images/simple_face.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
Images/speech_bubble.png
Normal file
BIN
Images/speech_bubble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
Images/thought_bubble.png
Normal file
BIN
Images/thought_bubble.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
Images/video_icon.png
Normal file
BIN
Images/video_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
15
helpers.py
15
helpers.py
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
137
moser/main.py
Normal 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
34
moser/moser_helpers.py
Normal 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
|
1
scene.py
1
scene.py
|
@ -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):
|
||||||
|
|
Loading…
Add table
Reference in a new issue