diff --git a/brachistochrone/curves.py b/brachistochrone/curves.py index 8573c90c..539173cf 100644 --- a/brachistochrone/curves.py +++ b/brachistochrone/curves.py @@ -153,29 +153,6 @@ class PathSlidingScene(Scene): "gravity" : 3, "delta_t" : 0.05 } - def get_time_slices(self, points): - dt_list = np.zeros(len(points)) - ds_list = np.apply_along_axis( - np.linalg.norm, - 1, - points[1:]-points[:-1] - ) - delta_y_list = np.abs(points[0, 1] - points[1:,1]) - delta_y_list += 0.001*(delta_y_list == 0) - v_list = self.gravity*np.sqrt(delta_y_list) - dt_list[1:] = ds_list / v_list - return np.cumsum(dt_list) - - def adjust_mobject_to_index(self, mobject, index, points): - point_a, point_b = points[index-1], points[index] - while np.all(point_a == point_b): - index += 1 - point_b = points[index] - theta = angle_of_vector(point_b - point_a) - mobject.rotate(theta) - mobject.shift(points[index]) - return mobject - def slide(self, mobject, path, roll = False): points = path.points time_slices = self.get_time_slices(points) @@ -208,6 +185,29 @@ class PathSlidingScene(Scene): self.add(self.slider) self.dither() + def get_time_slices(self, points): + dt_list = np.zeros(len(points)) + ds_list = np.apply_along_axis( + np.linalg.norm, + 1, + points[1:]-points[:-1] + ) + delta_y_list = np.abs(points[0, 1] - points[1:,1]) + delta_y_list += 0.001*(delta_y_list == 0) + v_list = self.gravity*np.sqrt(delta_y_list) + dt_list[1:] = ds_list / v_list + return np.cumsum(dt_list) + + def adjust_mobject_to_index(self, mobject, index, points): + point_a, point_b = points[index-1], points[index] + while np.all(point_a == point_b): + index += 1 + point_b = points[index] + theta = angle_of_vector(point_b - point_a) + mobject.rotate(theta) + mobject.shift(points[index]) + return mobject + def write_time(self, time): if hasattr(self, "time_mob"): self.remove(self.time_mob) @@ -223,6 +223,16 @@ class PathSlidingScene(Scene): theta = arc_length / radius mobject.rotate_in_place(-theta) + def add_cycloid_end_points(self): + cycloid = Cycloid() + point_a = Dot(cycloid.points[0]) + point_b = Dot(cycloid.points[-1]) + A = TexMobject("A").next_to(point_a, LEFT) + B = TexMobject("B").next_to(point_b, RIGHT) + self.add(point_a, point_b, A, B) + digest_locals(self) + + class TryManyPaths(PathSlidingScene): def construct(self): randy = Randolph() @@ -271,17 +281,17 @@ class TryManyPaths(PathSlidingScene): Line(DOWN, DOWN+3*RIGHT) ).ingest_sub_mobjects().highlight(GREEN) paths = [ - Line(7*LEFT, 7*RIGHT, color = RED_D), - LoopTheLoop(), - FunctionGraph( - lambda x : 0.05*(x**2)+0.1*np.sin(2*x) - ), Arc( angle = np.pi/2, radius = 3, start_angle = 4 ), + LoopTheLoop(), + Line(7*LEFT, 7*RIGHT, color = RED_D), sharp_corner, + FunctionGraph( + lambda x : 0.05*(x**2)+0.1*np.sin(2*x) + ), FunctionGraph( lambda x : x**2, x_min = -3, @@ -294,40 +304,60 @@ class TryManyPaths(PathSlidingScene): return paths + [cycloid] def align_paths(self, paths, target_path): - def path_displacement(path): - return path.points[-1]-path.points[0] - target = path_displacement(target_path) + start = target_path.points[0] + end = target_path.point[-1] for path in paths: - vect = path_displacement(path) - path.scale(np.linalg.norm(target)/np.linalg.norm(vect)) - path.rotate( - angle_of_vector(target) - \ - angle_of_vector(vect) - ) - path.shift(target_path.points[0]-path.points[0]) + path.position_endpoints_on(start, end) class RollingRandolph(PathSlidingScene): def construct(self): - cycloid = Cycloid() - point_a = Dot(cycloid.points[0]) - point_b = Dot(cycloid.points[-1]) - A = TexMobject("A").next_to(point_a, LEFT) - B = TexMobject("B").next_to(point_b, RIGHT) randy = Randolph() randy.scale(RANDY_SCALE_VAL) randy.shift(-randy.get_bottom()) + self.add_cycloid_end_points() + self.slide(randy, self.cycloid, roll = True) - self.add(point_a, point_b, A, B, cycloid) - self.slide(randy, cycloid, roll = True) - - - - - - - - + + +class NotTheCircle(PathSlidingScene): + def construct(self): + self.add_cycloid_end_points() + start = self.point_a.get_center() + end = self.point_b.get_center() + angle = 2*np.pi/3 + path = Arc(angle, radius = 3) + path.gradient_highlight(RED_D, WHITE) + radius = Line(ORIGIN, path.points[0]) + randy = Randolph() + randy.scale(RANDY_SCALE_VAL) + randy.shift(-randy.get_bottom()) + randy_copy = randy.copy() + words = TextMobject("Circular paths are good, \\\\ but still not the best") + words.shift(UP) + + self.play( + ShowCreation(path), + ApplyMethod( + radius.rotate, + angle, + path_func = path_along_arc(angle) + ) + ) + self.play(FadeOut(radius)) + self.play( + ApplyMethod( + path.position_endpoints_on, start, end, + path_func = path_along_arc(-angle) + ), + run_time = 3 + ) + self.adjust_mobject_to_index(randy_copy, 1, path.points) + self.play(FadeIn(randy_copy)) + self.remove(randy_copy) + self.slide(randy, path) + self.play(ShimmerIn(words)) + self.dither() diff --git a/drawing_images.py b/brachistochrone/drawing_images.py similarity index 59% rename from drawing_images.py rename to brachistochrone/drawing_images.py index ec8df6f8..0afd3cf8 100644 --- a/drawing_images.py +++ b/brachistochrone/drawing_images.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import numpy as np import itertools as it import operator as op @@ -17,16 +15,17 @@ from mobject.tex_mobject import TexMobject from mobject import Mobject from mobject.image_mobject import \ MobjectFromRegion, ImageMobject, MobjectFromPixelArray +from mobject.tex_mobject import TextMobject, TexMobject from animation.transform import \ Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ - FadeIn, FadeOut, GrowFromCenter + FadeIn, FadeOut, GrowFromCenter, ShimmerIn, ApplyMethod from animation.simple_animations import \ ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder from animation.playground import TurnInsideOut, Vibrate from topics.geometry import \ Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point -from topics.characters import Randolph, Mathematician +from topics.characters import Randolph, Mathematician, ThoughtBubble from topics.functions import ParametricFunction from topics.number_line import NumberPlane from region import Region, region_from_polygon_vertices @@ -134,7 +133,7 @@ class TracePicture(Scene): ("Steven_Strogatz",), ("Pierre_de_Fermat",), ("Galileo_Galilei",), - ("Jakob_Bernoulli",), + ("Jacob_Bernoulli",), ("Johann_Bernoulli2",), ] @@ -191,16 +190,110 @@ class TracePicture(Scene): - - - - - - - - - - +class JohannThinksHeIsBetter(Scene): + def construct(self): + names = [ + "Johann_Bernoulli2", + "Jacob_Bernoulli", + "Gottfried_Wilhelm_von_Leibniz", + ] + guys = [ + ImageMobject(name, invert = False) + for name in names + ] + johann = guys[0] + johann.scale(0.8) + pensive_johann = johann.copy() + pensive_johann.scale(0.25) + pensive_johann.to_corner(DOWN+LEFT) + comparitive_johann = johann.copy() + template = Square(side_length = 2) + comparitive_johann.replace(template) + comparitive_johann.shift(UP+LEFT) + greater_than = TexMobject(">") + greater_than.next_to(comparitive_johann) + for guy, name in zip(guys, names)[1:]: + guy.replace(template) + guy.next_to(greater_than) + name_mob = TextMobject(name.replace("_", " ")) + name_mob.scale(0.5) + name_mob.next_to(guy, DOWN) + guy.name_mob = name_mob + guy.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) + bubble = ThoughtBubble(initial_width = 12) + bubble.stretch_to_fit_height(6) + bubble.ingest_sub_mobjects() + bubble.pin_to(pensive_johann) + bubble.shift(DOWN) + point = Point(johann.get_corner(UP+RIGHT)) + + self.add(johann) + self.dither() + self.play( + Transform(johann, pensive_johann), + Transform(point, bubble), + run_time = 2 + ) + self.remove(point) + self.add(bubble) + weakling = guys[1] + self.play( + FadeIn(comparitive_johann), + ShowCreation(greater_than), + FadeIn(weakling) + ) + self.dither(2) + for guy in guys[2:]: + self.play( + DelayByOrder(Transform(weakling, guy)), + ShimmerIn(guy.name_mob) + ) + self.dither(3) + self.remove(guy.name_mob) + + +class NewtonVsJohann(Scene): + def construct(self): + newton, johann = [ + ImageMobject(name, invert = False).scale(0.5) + for name in "Newton", "Johann_Bernoulli2" + ] + greater_than = TexMobject(">") + newton.next_to(greater_than, RIGHT) + johann.next_to(greater_than, LEFT) + self.add(johann, greater_than, newton) + for i in range(2): + kwargs = { + "path_func" : counterclockwise_path(), + "run_time" : 2 + } + self.play( + ApplyMethod(newton.replace, johann, **kwargs), + ApplyMethod(johann.replace, newton, **kwargs), + ) + self.dither() + + +class JohannThinksOfFermat(Scene): + def construct(self): + johann, fermat = [ + ImageMobject(name, invert = False) + for name in "Johann_Bernoulli2", "Pierre_de_Fermat" + ] + johann.scale(0.2) + johann.to_corner(DOWN+LEFT) + bubble = ThoughtBubble(initial_width = 12) + bubble.stretch_to_fit_height(6) + bubble.pin_to(johann) + bubble.shift(DOWN) + bubble.add_content(fermat) + fermat.scale_in_place(0.4) + + + self.add(johann, bubble) + self.dither() + self.play(FadeIn(fermat)) + self.dither() diff --git a/brachistochrone/light.py b/brachistochrone/light.py index f238369e..77b265e9 100644 --- a/brachistochrone/light.py +++ b/brachistochrone/light.py @@ -4,7 +4,7 @@ import itertools as it from helpers import * from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject +from mobject import Mobject, Mobject1D from mobject.image_mobject import \ MobjectFromRegion, ImageMobject, MobjectFromPixelArray from topics.three_dimensions import Stars @@ -237,7 +237,7 @@ class ShowMultiplePathsThroughLens(ShowMultiplePathsScene): self.end_point = 2*RIGHT def get_paths(self): - alphas = [0.2, 0.45, 0.55, 0.8] + alphas = [0.25, 0.4, 0.58, 0.75] lower_right, upper_right, upper_left, lower_left = map( self.lens.point_from_proportion, alphas ) @@ -306,6 +306,219 @@ class ShowMultiplePathsInGlass(ShowMultiplePathsScene): ] +class MultilayeredGlass(PhotonScene): + DEFAULT_CONFIG = { + "num_discrete_layers" : 5, + "num_variables" : 3, + "top_color" : BLUE_E, + "bottom_color" : BLUE_A, + } + def construct(self): + self.cycloid = Cycloid(end_theta = np.pi) + self.top = self.cycloid.get_top()[1] + self.bottom = self.cycloid.get_bottom()[1]-1 + self.generate_layer_regions() + self.generate_discrete_path() + photon_run = self.photon_run_along_path( + self.augmented_path, + run_time = 1, + rate_func = rush_into + ) + + # self.continuous_to_smooth() + self.paint_layers() + self.show_layer_variables() + self.play(photon_run) + self.play(ShowCreation(self.discrete_path)) + self.isolate_bend_points() + # self.dither() + + def continuous_to_smooth(self): + continuous = self.get_continuous_background() + layers = Mobject(*[ + MobjectFromRegion(region, color) + for region, color in zip( + self.layer_regions, self.layer_colors + ) + ]) + layers.ingest_sub_mobjects() + + self.play(FadeIn(continuous)) + self.play(Transform(continuous, layers)) + self.remove(continuous) + self.paint_layers() + self.dither() + + def paint_layers(self): + # for region, color in zip(self.layer_regions, self.layer_colors): + # self.highlight_region(region, color) + for top, color in zip(self.layer_tops, self.layer_colors): + self.add(Line( + SPACE_WIDTH*LEFT+top*UP, SPACE_WIDTH*RIGHT+top*UP, + color = color + )) + + def get_continuous_background(self): + glass = MobjectFromRegion(Region( + lambda x, y : (y < self.top) & (y > self.bottom) + )) + glass.gradient_highlight(self.top_color, self.bottom_color) + glass.scale_in_place(0.99) + return glass + + def generate_layer_info(self): + self.layer_thickness = float(self.top-self.bottom)/self.num_discrete_layers + self.layer_tops = np.arange( + self.top, self.bottom, -self.layer_thickness + ) + top_rgb, bottom_rgb = [ + np.array(Color(color).get_rgb()) + for color in self.top_color, self.bottom_color + ] + epsilon = 1./(self.num_discrete_layers-1) + self.layer_colors = [ + Color(rgb = interpolate(top_rgb, bottom_rgb, alpha)) + for alpha in np.arange(0, 1+epsilon, epsilon) + ] + + def generate_layer_regions(self): + self.generate_layer_info() + self.layer_regions = [ + Region(lambda x, y : (y < top) & (y > top-self.layer_thickness)) + for top in self.layer_tops + ] + + def generate_discrete_path(self): + points = self.cycloid.points + indices = [ + np.argmin(np.abs(points[:, 1]-top)) + for top in self.layer_tops + ] + self.bend_points = points[indices[1:-1]] + self.discrete_path = Mobject1D(color = YELLOW) + for start, end in zip(indices, indices[1:]): + self.discrete_path.add_line( + points[start], points[end] + ) + self.augmented_path = self.discrete_path.copy() + self.augmented_path.add_line( + points[end], SPACE_WIDTH*RIGHT+(self.layer_tops[-1]-1)*UP + ) + + def show_layer_variables(self): + layer_top_pairs = zip( + self.layer_tops[:self.num_variables], + self.layer_tops[1:] + ) + v_equations = [] + start_ys = [] + end_ys = [] + center_paths = [] + braces = [] + for (top1, top2), x in zip(layer_top_pairs, it.count(1)): + eq_mob = TexMobject( + ["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"], + size = "\\Large" + ) + midpoint = UP*(top1+top2)/2 + eq_mob.shift(midpoint) + v_eq = eq_mob.split() + center_paths.append(Line( + midpoint+SPACE_WIDTH*LEFT, + midpoint+SPACE_WIDTH*RIGHT + )) + brace_endpoints = Mobject( + Point(self.top*UP+x*RIGHT), + Point(top2*UP+x*RIGHT) + ) + brace = Brace(brace_endpoints, RIGHT) + + start_y = TexMobject("y_%d"%x, size = "\\Large") + end_y = start_y.copy() + start_y.next_to(brace, RIGHT) + end_y.shift(v_eq[-1].get_center()) + end_y.shift(0.2*RIGHT) + + v_equations.append(v_eq) + start_ys.append(start_y) + end_ys.append(end_y) + braces.append(brace) + for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]): + photon_run = self.photon_run_along_path( + path, + rate_func = None + ) + self.play( + ShimmerIn(v_eq[0]), + photon_run, + run_time = time + ) + self.dither() + for start_y, brace in zip(start_ys, braces): + start_y.highlight(BLACK) + self.add(start_y) + self.play(GrowFromCenter(brace)) + self.dither() + quads = zip(v_equations, start_ys, end_ys, braces) + self.equations = [] + for v_eq, start_y, end_y, brace in quads: + self.remove(brace) + self.play( + ShowCreation(v_eq[1]), + ShowCreation(v_eq[2]), + Transform(start_y, end_y) + ) + + v_eq.append(start_y) + self.equations.append(Mobject(*v_eq)) + + def isolate_bend_points(self): + little_square = Square(side_length = 4, color = WHITE) + little_square.scale(0.25) + little_square.shift(self.bend_points[0]) + big_square = little_square.copy() + big_square.scale(4) + big_square.to_corner(UP+RIGHT) + + + first_time = True + for bend_point in self.bend_points: + if first_time: + self.play(ShowCreation(little_square)) + first_time = False + else: + self.remove(lines, big_square) + self.play(ApplyMethod( + little_square.shift, + bend_point - little_square.get_center() + )) + lines = self.lines_connecting_squares(little_square, big_square) + self.play( + ShowCreation(lines), + ShowCreation(big_square) + ) + self.dither(2) + + + + def lines_connecting_squares(self, square1, square2): + return Mobject(*[ + Line( + square1.get_corner(vect), + square2.get_corner(vect), + ) + for vect in [UP+LEFT, DOWN+LEFT] + ]).highlight(square1.get_color()) + + + + + +class MultilayeredGlassZoomIn(Scene): + def construct(self, layer_number): + pass + + diff --git a/brachistochrone/wordplay.py b/brachistochrone/wordplay.py index 586b8f73..fb912bd6 100644 --- a/brachistochrone/wordplay.py +++ b/brachistochrone/wordplay.py @@ -1,5 +1,6 @@ import numpy as np import itertools as it +import os from helpers import * @@ -26,13 +27,96 @@ from topics.functions import ParametricFunction, FunctionGraph from topics.number_line import NumberPlane from region import Region, region_from_polygon_vertices from scene import Scene +from generate_logo import LogoGeneration +from brachistochrone.drawing_images import sort_by_color + +class Intro(Scene): + def construct(self): + logo = ImageMobject("LogoGeneration", invert = False) + name_mob = TextMobject("3Blue1Brown").center() + name_mob.highlight("grey") + name_mob.shift(2*DOWN) + self.add(name_mob, logo) + + new_text = TextMobject(["with ", "Steven Strogatz"]) + new_text.next_to(name_mob, DOWN) + self.play(*[ + ShimmerIn(part) + for part in new_text.split() + ]) + self.dither() + with_word, steve = new_text.split() + steve_copy = steve.copy().center().to_edge(UP) + # logo.sort_points(lambda p : -np.linalg.norm(p)) + sort_by_color(logo) + self.play( + Transform(steve, steve_copy), + DelayByOrder(Transform(logo, Point())), + FadeOut(with_word), + FadeOut(name_mob), + run_time = 3 + ) + + +class IntroduceSteve(Scene): + def construct(self): + name = TextMobject("Steven Strogatz") + name.to_edge(UP) + contributions = TextMobject("Frequent Contributions") + contributions.scale(0.5).to_edge(RIGHT).shift(2*UP) + books_word = TextMobject("Books") + books_word.scale(0.5).to_edge(LEFT).shift(2*UP) + radio_lab, sci_fri, cornell, book2, book3, book4 = [ + ImageMobject(filename, invert = False, filter_color = WHITE) + for filename in [ + "radio_lab", + "science_friday", + "cornell", + "strogatz_book2", + "strogatz_book3", + "strogatz_book4", + ] + ] + book1 = ImageMobject("strogatz_book1", invert = False) + nyt = ImageMobject("new_york_times") + logos = [radio_lab, nyt, sci_fri] + books = [book1, book2, book3, book4] + + sample_size = Square(side_length = 2) + last = contributions + for image in logos: + image.replace(sample_size) + image.next_to(last, DOWN) + last = image + sci_fri.scale_in_place(0.9) + shift_val = 0 + sample_size.scale(0.75) + for book in books: + book.replace(sample_size) + book.next_to(books_word, DOWN) + book.shift(shift_val*(RIGHT+DOWN)) + shift_val += 0.5 + sample_size.scale(2) + cornell.replace(sample_size) + cornell.next_to(name, DOWN) + + Mobject(*logos+books+[cornell]).show() + + self.add(name) + self.play(FadeIn(cornell)) + self.play(ShimmerIn(contributions)) + for logo in logos: + self.play(FadeIn(logo)) + self.play(ShimmerIn(books_word)) + for book in books: + book.shift(5*LEFT) + self.play(ApplyMethod(book.shift, 5*RIGHT)) + self.dither() class DisectBrachistochroneWord(Scene): def construct(self): - word = TextMobject( - ["Bra", "chis", "to", "chrone"] - ) + word = TextMobject(["Bra", "chis", "to", "chrone"]) original_word = word.copy() dots = [] for part in word.split(): @@ -52,13 +136,29 @@ class DisectBrachistochroneWord(Scene): time = TextMobject("Time") time.next_to(overbrace2, UP) time.highlight(YELLOW) - chrono_example = TextMobject("As in ``Chronological''") + chrono_example = TextMobject(""" + As in ``Chronological'' \\\\ + or ``Synchronize'' + """) 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) + chrono_arrow = Arrow( + word.get_right(), + chrono_example.get_bottom(), + color = BLUE_D + ) + brachy_example = TextMobject("As in . . . brachydactyly?") + brachy_example.scale(0.5) + brachy_example.to_edge(LEFT) + brachy_example.shift(2*DOWN) + brachy_example.highlight(GREEN) + brachy_arrow = Arrow( + word.get_left(), + brachy_example.get_top(), + color = GREEN + ) pronunciation = TextMobject(["/br", "e", "kist","e","kr$\\bar{o}$n/"]) pronunciation.split()[1].rotate_in_place(np.pi) @@ -94,9 +194,119 @@ class DisectBrachistochroneWord(Scene): self.dither() self.play(ShimmerIn(shortest)) self.play(ShimmerIn(time)) + for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]: + self.play( + ShowCreation(ar), + ShimmerIn(ex) + ) self.dither() - self.play( - ShowCreation(chrono_arrow), - ShimmerIn(chrono_example) + + +class FermatsPrincipleStatement(Scene): + def construct(self): + words = TextMobject([ + "Fermat's principle:", + """ + If a beam of light travels + from point $A$ to $B$, it does so along the + fastest path possible. + """ + ]) + words.split()[0].highlight(BLUE) + everything = MobjectFromRegion(Region()) + everything.scale(0.9) + angles = np.apply_along_axis( + angle_of_vector, 1, everything.points ) - self.dither() \ No newline at end of file + norms = np.apply_along_axis( + np.linalg.norm, 1, everything.points + ) + norms -= np.min(norms) + norms /= np.max(norms) + alphas = 0.25 + 0.75 * norms * (1 + np.sin(12*angles))/2 + everything.rgbs = alphas.repeat(3).reshape((len(alphas), 3)) + + Mobject(everything, words).show() + + everything.sort_points(np.linalg.norm) + self.add(words) + self.play( + DelayByOrder(FadeIn(everything, run_time = 3)), + Animation(words) + ) + self.play( + ApplyMethod(everything.highlight, WHITE), + ) + self.dither() + +class VideoProgression(Scene): + def construct(self): + all_topics = [ + TextMobject(word, size = "\\Huge") + for word in [ + "Brachistochrone", + "Light through \\\\ multilayered glass", + "Light from \\\\ air into glass", + "Lifeguard problem", + "Springs and rod", + "Snell's Law", + "Cycloid", + "Mark Levi's cleverness", + ] + ] + brachy, multi_glass, bi_glass, lifeguard, springs, \ + snell, cycloid, levi = all_topics + positions = [ + (multi_glass, brachy, DOWN, "both"), + (bi_glass, multi_glass, LEFT, "to"), + (lifeguard, bi_glass, UP, "both"), + (springs, bi_glass, DOWN, "both"), + (snell, springs, RIGHT, "from"), + (cycloid, multi_glass, RIGHT, "from"), + (cycloid, snell, UP+RIGHT, "from"), + (levi, cycloid, UP, "to"), + ] + arrows = [] + for mob1, mob2, direction, arrow_type in positions: + hasarrow = hasattr(mob1, "arrow") + if not hasarrow: + mob1.next_to(mob2, direction, buff = 3) + arrow = Arrow(mob1, mob2) + if arrow_type in ["to", "from"]: + arrow.highlight(GREEN) + if arrow_type == "from": + arrow.rotate_in_place(np.pi) + elif arrow_type == "both": + arrow.highlight(BLUE_D) + arrow.add(arrow.copy().rotate_in_place(np.pi)) + arrows.append(arrow) + if hasarrow: + mob1.arrow.add(arrow) + else: + mob1.arrow = arrow + everything = Mobject(*all_topics+arrows) + everything.scale(0.7) + everything.center() + everything.to_edge(UP) + everything.show() + + self.add(brachy) + for mob in all_topics[1:]: + self.play(ApplyMethod(mob.highlight, YELLOW)) + self.play(ShowCreation(mob.arrow)) + self.dither() + mob.highlight(WHITE) + + + + + + + + + + + + + + diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 6390721f..cbee4020 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -31,6 +31,7 @@ class ImageMobject(Mobject): os.path.join(IMAGE_DIR, image_file), os.path.join(IMAGE_DIR, image_file + ".jpg"), os.path.join(IMAGE_DIR, image_file + ".png"), + os.path.join(IMAGE_DIR, image_file + ".gif"), ] for path in possible_paths: if os.path.exists(path): diff --git a/transform_article.py b/transform_article.py deleted file mode 100644 index fae75862..00000000 --- a/transform_article.py +++ /dev/null @@ -1,344 +0,0 @@ -from topics import * -from animation import * - - -def half_plane(): - plane = NumberPlane( - x_radius = SPACE_WIDTH/2, - x_unit_to_spatial_width = 0.5, - y_unit_to_spatial_height = 0.5, - x_faded_line_frequency = 0, - y_faded_line_frequency = 0, - density = 4*DEFAULT_POINT_DENSITY_1D, - ) - plane.add_coordinates( - x_vals = range(-6, 7, 2), - y_vals = range(-6, 7, 2) - ) - return plane - -class SingleVariableFunction(Scene): - args_list = [ - (lambda x : x**2 - 3, "ShiftedSquare", True), - (lambda x : x**2 - 3, "ShiftedSquare", False), - ] - - @staticmethod - def args_to_string(func, name, separate_lines): - return name + ("SeparateLines" if separate_lines else "") - - def construct(self, func, name, separate_lines): - base_line = NumberLine(color = "grey") - moving_line = NumberLine( - tick_frequency = 1, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - base_line.add_numbers() - def point_function((x, y, z)): - return (func(x), y, z) - target = moving_line.copy().apply_function(point_function) - - transform_config = { - "run_time" : 3, - "path_func" : path_along_arc(np.pi/4) - } - - if separate_lines: - numbers = moving_line.get_number_mobjects(*range(-7, 7)) - negative_numbers = [] - for number in numbers: - number.highlight(GREEN_E) - number.shift(-2*moving_line.get_vertical_number_offset()) - center = number.get_center() - target_num = number.copy() - target_num.shift(point_function(center) - center) - target.add(target_num) - if center[0] < -0.5: - negative_numbers.append(number) - moving_line.add(*numbers) - base_line.shift(DOWN) - target.shift(DOWN) - moving_line.shift(UP) - - self.add(base_line, moving_line) - self.dither(3) - self.play(Transform(moving_line, target, **transform_config)) - if separate_lines: - self.play(*[ - ApplyMethod(mob.shift, 0.4*UP) - for mob in negative_numbers - ]) - self.dither(3) - - -class LineToPlaneFunction(Scene): - args_list = [ - (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", []), - (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", [ - ("0", "(1, 0)", 0), - ("\\frac{\\pi}{2}", "(0, \\pi / 4)", np.pi/2), - ("\\pi", "(-1, 0)", np.pi), - ]) - ] - - @staticmethod - def args_to_string(func, name, numbers_to_follow): - return name + ("FollowingNumbers" if numbers_to_follow else "") - - def construct(self, func, name, numbers_to_follow): - line = NumberLine( - unit_length_to_spatial_width = 0.5, - tick_frequency = 1, - number_at_center = 6, - numerical_radius = 6, - numbers_with_elongated_ticks = [0, 12], - density = 3*DEFAULT_POINT_DENSITY_1D - ) - line.to_edge(LEFT) - line_copy = line.copy() - line.add_numbers(*range(0, 14, 2)) - divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) - plane = half_plane() - plane.sub_mobjects = [] - plane.filter_out( - lambda (x, y, z) : abs(x) > 0.1 and abs(y) > 0.1 - ) - plane.shift(0.5*SPACE_WIDTH*RIGHT) - self.add(line, divider, plane) - - def point_function(point): - x, y = func(line.point_to_number(point)) - return plane.num_pair_to_point((x, y)) - - target = line_copy.copy().apply_function(point_function) - target.highlight() - anim_config = {"run_time" : 3} - anims = [Transform(line_copy, target, **anim_config)] - - colors = iter([BLUE_B, GREEN_D, RED_D]) - for input_tex, output_tex, number in numbers_to_follow: - center = line.number_to_point(number) - dot = Dot(center, color = colors.next()) - anims.append(ApplyMethod( - dot.shift, - point_function(center) - center, - **anim_config - )) - label = TexMobject(input_tex) - label.shift(center + 2*UP) - arrow = Arrow(label, dot) - self.add(label) - self.play(ShowCreation(arrow), ShowCreation(dot)) - self.dither() - self.remove(arrow, label) - - - self.dither(2) - self.play(*anims) - self.dither() - - for input_tex, output_tex, number in numbers_to_follow: - point = plane.num_pair_to_point(func(number)) - label = TexMobject(output_tex) - side_shift = LEFT if number == np.pi else RIGHT - label.shift(point, 2*UP, side_shift) - arrow = Arrow(label, point) - self.add(label) - self.play(ShowCreation(arrow)) - self.dither(2) - self.remove(arrow, label) - -class PlaneToPlaneFunctionSeparatePlanes(Scene): - args_list = [ - (lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic") - ] - @staticmethod - def args_to_string(func, name): - return name - - def construct(self, func, name): - shift_factor = 0.55 - in_plane = half_plane().shift(shift_factor*SPACE_WIDTH*LEFT) - out_plane = half_plane().shift(shift_factor*SPACE_WIDTH*RIGHT) - divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) - self.add(in_plane, out_plane, divider) - - plane_copy = in_plane.copy() - plane_copy.sub_mobjects = [] - - def point_function(point): - result = np.array(func((point*2 + 2*shift_factor*SPACE_WIDTH*RIGHT)[:2])) - result = np.append(result/2, [0]) - return result + shift_factor*SPACE_WIDTH*RIGHT - - target = plane_copy.copy().apply_function(point_function) - target.highlight(GREEN_B) - - anim_config = {"run_time" : 5} - - self.dither() - self.play(Transform(plane_copy, target, **anim_config)) - self.dither() - -class PlaneToPlaneFunction(Scene): - args_list = [ - (lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic") - ] - @staticmethod - def args_to_string(func, name): - return name - - def construct(self, func, name): - plane = NumberPlane() - background = NumberPlane(color = "grey") - background.add_coordinates() - anim_config = {"run_time" : 3} - - def point_function(point): - return np.append(func(point[:2]), [0]) - - self.add(background, plane) - self.dither(2) - self.play(ApplyPointwiseFunction(point_function, plane, **anim_config)) - self.dither(3) - -class PlaneToLineFunction(Scene): - args_list = [ - (lambda (x, y) : x**2 + y**2, "Bowl"), - ] - - @staticmethod - def args_to_string(func, name): - return name - - def construct(self, func, name): - line = NumberLine( - color = GREEN, - unit_length_to_spatial_width = 0.5, - tick_frequency = 1, - number_at_center = 6, - numerical_radius = 6, - numbers_with_elongated_ticks = [0, 12], - ).to_edge(RIGHT) - line.add_numbers() - plane = half_plane().to_edge(LEFT, buff = 0) - - divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) - line_left = line.number_to_point(0) - def point_function(point): - shifter = 0.5*SPACE_WIDTH*RIGHT - return func((point+shifter)[:2])*RIGHT + line_left - - self.add(line, plane, divider) - self.dither() - plane.sub_mobjects = [] - self.play(ApplyPointwiseFunction(point_function, plane)) - self.dither() - - - -class PlaneToSpaceFunction(Scene): - args_list = [ - (lambda (x, y) : (x*x, x*y, y*y), "Quadratic"), - ] - - @staticmethod - def args_to_string(func, name): - return name - - def construct(self, func, name): - plane = half_plane().shift(0.5*SPACE_WIDTH*LEFT) - divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) - axes = XYZAxes() - axes.filter_out(lambda p : np.linalg.norm(p) > 3) - rot_kwargs = { - "run_time" : 3, - "radians" : 0.3*np.pi, - "axis" : [0.1, 1, 0.1], - } - axes.to_edge(RIGHT).shift(DOWN) - dampening_factor = 0.1 - def point_function((x, y, z)): - return dampening_factor*np.array(func((x, y))) - target = NumberPlane().apply_function(point_function) - target.highlight("yellow") - target.shift(axes.get_center()) - - self.add(plane, divider, axes) - self.play(Rotating(axes, **rot_kwargs)) - - target.rotate_in_place(rot_kwargs["radians"]) - self.play( - TransformAnimations( - Animation(plane.copy()), - Rotating(target, **rot_kwargs), - rate_func = smooth - ), - Rotating(axes, **rot_kwargs) - ) - axes.add(target) - self.clear() - self.add(plane, divider, axes) - self.play(Rotating(axes, **rot_kwargs)) - self.clear() - for i in range(5): - self.play(Rotating(axes, **rot_kwargs)) - - -class SpaceToSpaceFunction(Scene): - args_list = [ - (lambda (x, y, z) : (y*z, x*z, x*y), "Quadratic"), - ] - - @staticmethod - def args_to_string(func, name): - return name - - def construct(self, func, name): - space = SpaceGrid() - rot_kwargs = { - "run_time" : 10, - "radians" : 2*np.pi/5, - "axis" : [0.1, 1, 0.1], - "in_place" : False, - } - axes = XYZAxes() - target = space.copy().apply_function(func) - - self.play( - TransformAnimations( - Rotating(space, **rot_kwargs), - Rotating(target, **rot_kwargs), - rate_func = squish_rate_func(smooth, 0.3, 0.7) - ), - Rotating(axes, **rot_kwargs) - ) - axes.add(space) - self.play(Rotating(axes, **rot_kwargs)) - - - - - - - - - - - - - - - - - - - - - - - - - - -