diff --git a/brachistochrone.py b/brachistochrone/curves.py similarity index 67% rename from brachistochrone.py rename to brachistochrone/curves.py index 559833f3..fe1ac860 100644 --- a/brachistochrone.py +++ b/brachistochrone/curves.py @@ -1,33 +1,26 @@ -#!/usr/bin/env python - import numpy as np import itertools as it -import operator as op -import sys -import inspect -from PIL import Image -import cv2 -import random -from scipy.spatial.distance import cdist -from scipy import ndimage from helpers import * -from mobject.tex_mobject import TexMobject, TextMobject +from mobject.tex_mobject import TexMobject, TextMobject, Brace from mobject import Mobject from mobject.image_mobject import \ MobjectFromRegion, ImageMobject, MobjectFromPixelArray +from topics.three_dimensions import Stars +from animation import Animation from animation.transform import \ Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ ShimmerIn from animation.simple_animations import \ - ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder + ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ + ShowPassingFlash from animation.playground import TurnInsideOut, Vibrate from topics.geometry import \ Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ - Arc + Arc, FilledRectangle from topics.characters import Randolph, Mathematician from topics.functions import ParametricFunction, FunctionGraph from topics.number_line import NumberPlane @@ -36,28 +29,7 @@ from scene import Scene RANDY_SCALE_VAL = 0.3 -########### -def wavify(mobject): - tangent_vectors = mobject.points[1:]-mobject.points[:-1] - lengths = np.apply_along_axis( - np.linalg.norm, 1, tangent_vectors - ) - thick_lengths = lengths.repeat(3).reshape((len(lengths), 3)) - unit_tangent_vectors = tangent_vectors/thick_lengths - rot_matrix = np.transpose(rotation_matrix(np.pi/2, OUT)) - normal_vectors = np.dot(unit_tangent_vectors, rot_matrix) - # total_length = np.sum(lengths) - times = np.cumsum(lengths) - nudge_sizes = 0.1*np.sin(2*np.pi*times) - thick_nudge_sizes = nudge_sizes.repeat(3).reshape((len(nudge_sizes), 3)) - nudges = thick_nudge_sizes*normal_vectors - result = mobject.copy() - result.points[1:] += nudges - return result - - -########### class Cycloid(ParametricFunction): DEFAULT_CONFIG = { @@ -98,6 +70,84 @@ class LoopTheLoop(ParametricFunction): ) +class SlideWordDownCycloid(Animation): + DEFAULT_CONFIG = { + "rate_func" : None, + "run_time" : 8 + } + def __init__(self, word, **kwargs): + self.path = Cycloid(end_theta = np.pi) + word_mob = TextMobject(list(word)) + end_word = word_mob.copy() + end_word.shift(-end_word.get_bottom()) + end_word.shift(self.path.get_corner(DOWN+RIGHT)) + end_word.shift(3*RIGHT) + self.end_letters = end_word.split() + for letter in word_mob.split(): + letter.center() + letter.angle = 0 + unit_interval = np.arange(0, 1, 1./len(word)) + self.start_times = 0.5*(1-(unit_interval)) + Animation.__init__(self, word_mob, **kwargs) + + def update_mobject(self, alpha): + virtual_times = 2*(alpha - self.start_times) + cut_offs = [ + 0.1, + 0.3, + 0.7, + ] + for letter, time, end_letter in zip( + self.mobject.split(), virtual_times, self.end_letters + ): + time = max(time, 0) + time = min(time, 1) + if time < cut_offs[0]: + brightness = time/cut_offs[0] + letter.rgbs = brightness*np.ones(letter.rgbs.shape) + position = self.path.points[0] + angle = 0 + elif time < cut_offs[1]: + alpha = (time-cut_offs[0])/(cut_offs[1]-cut_offs[0]) + angle = -rush_into(alpha)*np.pi/2 + position = self.path.points[0] + elif time < cut_offs[2]: + alpha = (time-cut_offs[1])/(cut_offs[2]-cut_offs[1]) + index = int(alpha*self.path.get_num_points()) + position = self.path.points[index] + try: + angle = angle_of_vector( + self.path.points[index+1] - \ + self.path.points[index] + ) + except: + angle = letter.angle + else: + alpha = (time-cut_offs[2])/(1-cut_offs[2]) + start = self.path.points[-1] + end = end_letter.get_bottom() + position = interpolate(start, end, rush_from(alpha)) + angle = 0 + + letter.shift(position-letter.get_bottom()) + letter.rotate_in_place(angle-letter.angle) + letter.angle = angle + + +class BrachistochroneWordSliding(Scene): + def construct(self): + anim = SlideWordDownCycloid("Brachistochrone") + anim.path.gradient_highlight(WHITE, BLUE_E) + self.play(ShowCreation(anim.path)) + self.play(anim) + self.dither() + self.play( + FadeOut(anim.path), + ApplyMethod(anim.mobject.center) + ) + + + class PathSlidingScene(Scene): DEFAULT_CONFIG = { "gravity" : 3, @@ -273,22 +323,38 @@ class RollingRandolph(PathSlidingScene): -class SimplePhoton(Scene): +class OceanScene(Scene): def construct(self): - photon = wavify(Cycloid()) - photon.highlight(YELLOW) - shaddow = photon.copy().highlight(BLACK) + self.rolling_waves() - self.play( - ShowCreation(photon, rate_func = None), - ShowCreation( - shaddow, - rate_func = lambda t : max(0, t-0.1) + def rolling_waves(self): + if not hasattr(self, "ocean"): + self.setup_ocean() + for state in self.ocean_states: + self.play(Transform(self.ocean, state)) + + + def setup_ocean(self): + def func(points): + result = np.zeros(points.shape) + result[:,1] = 0.25 * np.sin(points[:,0]) * np.sin(points[:,1]) + return result + + self.ocean_states = [] + for unit in -1, 1: + ocean = FilledRectangle( + color = BLUE_D, + density = 25 ) - ) - self.dither() - - + nudges = unit*func(ocean.points) + ocean.points += nudges + alphas = nudges[:,1] + alphas -= np.min(alphas) + whites = np.ones(ocean.rgbs.shape) + thick_alphas = alphas.repeat(3).reshape((len(alphas), 3)) + ocean.rgbs = interpolate(ocean.rgbs, whites, thick_alphas) + self.ocean_states.append(ocean) + self.ocean = self.ocean_states[1].copy() diff --git a/brachistochrone/light.py b/brachistochrone/light.py new file mode 100644 index 00000000..889bce71 --- /dev/null +++ b/brachistochrone/light.py @@ -0,0 +1,65 @@ +import numpy as np +import itertools as it + +from helpers import * + +from mobject.tex_mobject import TexMobject, TextMobject, Brace +from mobject import Mobject +from mobject.image_mobject import \ + MobjectFromRegion, ImageMobject, MobjectFromPixelArray +from topics.three_dimensions import Stars + +from animation import Animation +from animation.transform import \ + Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ + FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ + ShimmerIn +from animation.simple_animations import \ + ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ + ShowPassingFlash +from animation.playground import TurnInsideOut, Vibrate +from topics.geometry import \ + Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ + Arc, FilledRectangle +from topics.characters import Randolph, Mathematician +from topics.functions import ParametricFunction, FunctionGraph +from topics.number_line import NumberPlane +from region import Region, region_from_polygon_vertices +from scene import Scene + +class PhotonScene(Scene): + def wavify(self, mobject): + tangent_vectors = mobject.points[1:]-mobject.points[:-1] + lengths = np.apply_along_axis( + np.linalg.norm, 1, tangent_vectors + ) + thick_lengths = lengths.repeat(3).reshape((len(lengths), 3)) + unit_tangent_vectors = tangent_vectors/thick_lengths + rot_matrix = np.transpose(rotation_matrix(np.pi/2, OUT)) + normal_vectors = np.dot(unit_tangent_vectors, rot_matrix) + # total_length = np.sum(lengths) + times = np.cumsum(lengths) + nudge_sizes = 0.1*np.sin(2*np.pi*times) + thick_nudge_sizes = nudge_sizes.repeat(3).reshape((len(nudge_sizes), 3)) + nudges = thick_nudge_sizes*normal_vectors + result = mobject.copy() + result.points[1:] += nudges + return result + + def photon_along_path(self, path, color = YELLOW, run_time = 1): + photon = self.wavify(path) + photon.highlight(color) + self.play(ShowPassingFlash( + photon, + run_time = run_time, + rate_func = None + )) + + +class SimplePhoton(PhotonScene): + def construct(self): + text = TextMobject("Light") + text.to_edge(UP) + self.play(ShimmerIn(text)) + self.photon_along_path(Cycloid()) + self.dither() \ No newline at end of file diff --git a/brachistochrone/metaphors.py b/brachistochrone/metaphors.py new file mode 100644 index 00000000..bfd4d614 --- /dev/null +++ b/brachistochrone/metaphors.py @@ -0,0 +1,62 @@ +import numpy as np +import itertools as it + +from helpers import * + +from mobject.tex_mobject import TexMobject, TextMobject, Brace +from mobject import Mobject +from mobject.image_mobject import \ + MobjectFromRegion, ImageMobject, MobjectFromPixelArray +from topics.three_dimensions import Stars + +from animation import Animation +from animation.transform import \ + Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ + FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ + ShimmerIn +from animation.simple_animations import \ + ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ + ShowPassingFlash +from animation.playground import TurnInsideOut, Vibrate +from topics.geometry import \ + Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ + Arc, FilledRectangle +from topics.characters import Randolph, Mathematician +from topics.functions import ParametricFunction, FunctionGraph +from topics.number_line import NumberPlane +from region import Region, region_from_polygon_vertices +from scene import Scene + + +class OceanScene(Scene): + def construct(self): + self.rolling_waves() + + def rolling_waves(self): + if not hasattr(self, "ocean"): + self.setup_ocean() + for state in self.ocean_states: + self.play(Transform(self.ocean, state)) + + + def setup_ocean(self): + def func(points): + result = np.zeros(points.shape) + result[:,1] = 0.25 * np.sin(points[:,0]) * np.sin(points[:,1]) + return result + + self.ocean_states = [] + for unit in -1, 1: + ocean = FilledRectangle( + color = BLUE_D, + density = 25 + ) + nudges = unit*func(ocean.points) + ocean.points += nudges + alphas = nudges[:,1] + alphas -= np.min(alphas) + whites = np.ones(ocean.rgbs.shape) + thick_alphas = alphas.repeat(3).reshape((len(alphas), 3)) + ocean.rgbs = interpolate(ocean.rgbs, whites, thick_alphas) + self.ocean_states.append(ocean) + self.ocean = self.ocean_states[1].copy() \ No newline at end of file diff --git a/brachistochrone/wordplay.py b/brachistochrone/wordplay.py new file mode 100644 index 00000000..586b8f73 --- /dev/null +++ b/brachistochrone/wordplay.py @@ -0,0 +1,102 @@ +import numpy as np +import itertools as it + +from helpers import * + +from mobject.tex_mobject import TexMobject, TextMobject, Brace +from mobject import Mobject +from mobject.image_mobject import \ + MobjectFromRegion, ImageMobject, MobjectFromPixelArray +from topics.three_dimensions import Stars + +from animation import Animation +from animation.transform import \ + Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ + FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ + ShimmerIn +from animation.simple_animations import \ + ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ + ShowPassingFlash +from animation.playground import TurnInsideOut, Vibrate +from topics.geometry import \ + Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ + Arc, FilledRectangle +from topics.characters import Randolph, Mathematician +from topics.functions import ParametricFunction, FunctionGraph +from topics.number_line import NumberPlane +from region import Region, region_from_polygon_vertices +from scene import Scene + + +class DisectBrachistochroneWord(Scene): + def construct(self): + word = TextMobject( + ["Bra", "chis", "to", "chrone"] + ) + original_word = word.copy() + dots = [] + for part in word.split(): + if dots: + part.next_to(dots[-1], buff = 0.06) + dot = TexMobject("\\cdot") + dot.next_to(part, buff = 0.06) + dots.append(dot) + dots = Mobject(*dots[:-1]) + dots.shift(0.1*DOWN) + Mobject(word, dots).center() + overbrace1 = Brace(Mobject(*word.split()[:-1]), UP) + overbrace2 = Brace(word.split()[-1], UP) + shortest = TextMobject("Shortest") + shortest.next_to(overbrace1, UP) + shortest.highlight(YELLOW) + time = TextMobject("Time") + time.next_to(overbrace2, UP) + time.highlight(YELLOW) + chrono_example = TextMobject("As in ``Chronological''") + chrono_example.scale(0.5) + chrono_example.to_edge(RIGHT) + chrono_example.shift(2*UP) + chrono_example.highlight(BLUE_D) + chrono_arrow = Arrow(word.split()[-1], chrono_example) + chrono_arrow.highlight(BLUE_D) + + pronunciation = TextMobject(["/br", "e", "kist","e","kr$\\bar{o}$n/"]) + pronunciation.split()[1].rotate_in_place(np.pi) + pronunciation.split()[3].rotate_in_place(np.pi) + pronunciation.scale(0.7) + pronunciation.shift(DOWN) + + latin = TextMobject(list("Latin")) + greek = TextMobject(list("Greek")) + for mob in latin, greek: + mob.to_edge(LEFT) + question_mark = TextMobject("?").next_to(greek, buff = 0.1) + stars = Stars().highlight(BLACK) + stars.scale(0.5).shift(question_mark.get_center()) + + self.play(Transform(original_word, word), ShowCreation(dots)) + self.play(ShimmerIn(pronunciation)) + self.dither() + self.play( + GrowFromCenter(overbrace1), + GrowFromCenter(overbrace2) + ) + self.dither() + self.play(ShimmerIn(latin)) + self.play(FadeIn(question_mark)) + self.play(Transform( + latin, greek, + path_func = counterclockwise_path() + )) + self.dither() + self.play(Transform(question_mark, stars)) + self.remove(stars) + self.dither() + self.play(ShimmerIn(shortest)) + self.play(ShimmerIn(time)) + self.dither() + self.play( + ShowCreation(chrono_arrow), + ShimmerIn(chrono_example) + ) + self.dither() \ No newline at end of file