diff --git a/Images/simple_face.png b/Images/simple_face.png new file mode 100644 index 00000000..4646e467 Binary files /dev/null and b/Images/simple_face.png differ diff --git a/Images/speech_bubble.png b/Images/speech_bubble.png new file mode 100644 index 00000000..10dc0cbe Binary files /dev/null and b/Images/speech_bubble.png differ diff --git a/Images/thought_bubble.png b/Images/thought_bubble.png new file mode 100644 index 00000000..f939c2c2 Binary files /dev/null and b/Images/thought_bubble.png differ diff --git a/Images/video_icon.png b/Images/video_icon.png new file mode 100644 index 00000000..9c1aa35a Binary files /dev/null and b/Images/video_icon.png differ diff --git a/animation.py b/animation.py index bbed9f4e..38e30a5a 100644 --- a/animation.py +++ b/animation.py @@ -10,6 +10,7 @@ import inspect from images2gif import writeGif from helpers import * +from constants import * from mobject import Mobject class Animation(object): diff --git a/constants.py b/constants.py index c6caffa2..8b725318 100644 --- a/constants.py +++ b/constants.py @@ -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 diff --git a/generate_logo.py b/generate_logo.py index 4567d4c0..17a9764e 100644 --- a/generate_logo.py +++ b/generate_logo.py @@ -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) diff --git a/helpers.py b/helpers.py index ff286c70..fd060610 100644 --- a/helpers.py +++ b/helpers.py @@ -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) diff --git a/mobject.py b/mobject.py index 9cec9f93..237c0a28 100644 --- a/mobject.py +++ b/mobject.py @@ -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) diff --git a/moser/intro.py b/moser/intro.py index 1c5d1426..db3ae295 100644 --- a/moser/intro.py +++ b/moser/intro.py @@ -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") + diff --git a/moser/main.py b/moser/main.py new file mode 100644 index 00000000..e37654a4 --- /dev/null +++ b/moser/main.py @@ -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") diff --git a/moser/moser_helpers.py b/moser/moser_helpers.py new file mode 100644 index 00000000..e1ba9b6f --- /dev/null +++ b/moser/moser_helpers.py @@ -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 diff --git a/scene.py b/scene.py index da1ca609..1ea1f4cb 100644 --- a/scene.py +++ b/scene.py @@ -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):