diff --git a/brachistochrone/__init__.py b/brachistochrone/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/brachistochrone/curves.py b/brachistochrone/curves.py deleted file mode 100644 index ccc410ac..00000000 --- a/brachistochrone/curves.py +++ /dev/null @@ -1,719 +0,0 @@ -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 \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import * -from topics.characters import Randolph, Mathematician -from topics.functions import ParametricFunction, FunctionGraph -from topics.number_line import * -from mobject.region import Region, region_from_polygon_vertices -from scene import Scene - -RANDY_SCALE_VAL = 0.3 - - - -class Cycloid(ParametricFunction): - CONFIG = { - "point_a" : 6*LEFT+3*UP, - "radius" : 2, - "end_theta" : 3*np.pi/2, - "density" : 5*DEFAULT_POINT_DENSITY_1D, - "color" : YELLOW - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - ParametricFunction.__init__(self, self.pos_func, **kwargs) - - def pos_func(self, t): - T = t*self.end_theta - return self.point_a + self.radius * np.array([ - T - np.sin(T), - np.cos(T) - 1, - 0 - ]) - -class LoopTheLoop(ParametricFunction): - CONFIG = { - "color" : YELLOW_D, - "density" : 10*DEFAULT_POINT_DENSITY_1D - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - def func(t): - t = (6*np.pi/2)*(t-0.5) - return (t/2-np.sin(t))*RIGHT + \ - (np.cos(t)+(t**2)/10)*UP - ParametricFunction.__init__(self, func, **kwargs) - - -class SlideWordDownCycloid(Animation): - 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): - CONFIG = { - "gravity" : 3, - "delta_t" : 0.05, - "dither_and_add" : True, - "show_time" : True, - } - def slide(self, mobject, path, roll = False, ceiling = None): - points = path.points - time_slices = self.get_time_slices(points, ceiling = ceiling) - curr_t = 0 - last_index = 0 - curr_index = 1 - if self.show_time: - self.t_equals = TexMobject("t = ") - self.t_equals.shift(3.5*UP+4*RIGHT) - self.add(self.t_equals) - while curr_index < len(points): - self.slider = mobject.copy() - self.adjust_mobject_to_index( - self.slider, curr_index, points - ) - if roll: - distance = np.linalg.norm( - points[curr_index] - points[last_index] - ) - self.roll(mobject, distance) - self.add(self.slider) - if self.show_time: - self.write_time(curr_t) - self.dither(self.frame_duration) - self.remove(self.slider) - curr_t += self.delta_t - last_index = curr_index - while time_slices[curr_index] < curr_t: - curr_index += 1 - if curr_index == len(points): - break - if self.dither_and_add: - self.add(self.slider) - self.dither() - else: - return self.slider - - def get_time_slices(self, points, ceiling = None): - dt_list = np.zeros(len(points)) - ds_list = np.apply_along_axis( - np.linalg.norm, - 1, - points[1:]-points[:-1] - ) - if ceiling is None: - ceiling = points[0, 1] - delta_y_list = np.abs(ceiling - 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]) - self.midslide_action(point_a, theta) - return mobject - - def midslide_action(self, point, angle): - pass - - def write_time(self, time): - if hasattr(self, "time_mob"): - self.remove(self.time_mob) - digits = map(TexMobject, "%.2f"%time) - digits[0].next_to(self.t_equals, buff = 0.1) - for left, right in zip(digits, digits[1:]): - right.next_to(left, buff = 0.1, aligned_edge = DOWN) - self.time_mob = Mobject(*digits) - self.add(self.time_mob) - - def roll(self, mobject, arc_length): - radius = mobject.get_width()/2 - 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() - randy.shift(-randy.get_bottom()) - self.slider = randy.copy() - randy.scale(RANDY_SCALE_VAL) - paths = self.get_paths() - point_a = Dot(paths[0].points[0]) - point_b = Dot(paths[0].points[-1]) - A = TexMobject("A").next_to(point_a, LEFT) - B = TexMobject("B").next_to(point_b, RIGHT) - for point, tex in [(point_a, A), (point_b, B)]: - self.play(ShowCreation(point)) - self.play(ShimmerIn(tex)) - self.dither() - curr_path = None - for path in paths: - new_slider = self.adjust_mobject_to_index( - randy.copy(), 1, path.points - ) - if curr_path is None: - curr_path = path - self.play(ShowCreation(curr_path)) - else: - self.play(Transform(curr_path, path)) - self.play(Transform(self.slider, new_slider)) - self.dither() - self.remove(self.slider) - self.slide(randy, curr_path) - self.clear() - self.add(point_a, point_b, A, B, curr_path) - text = self.get_text() - text.to_edge(UP) - self.play(ShimmerIn(text)) - for path in paths: - self.play(Transform( - curr_path, path, - path_func = path_along_arc(np.pi/2), - run_time = 3 - )) - - def get_text(self): - return TextMobject("Which path is fastest?") - - def get_paths(self): - sharp_corner = Mobject( - Line(3*UP+LEFT, LEFT), - Arc(angle = np.pi/2, start_angle = np.pi), - Line(DOWN, DOWN+3*RIGHT) - ).ingest_sub_mobjects().highlight(GREEN) - paths = [ - 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, - x_max = 2, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - ] - cycloid = Cycloid() - self.align_paths(paths, cycloid) - return paths + [cycloid] - - def align_paths(self, paths, target_path): - start = target_path.points[0] - end = target_path.points[-1] - for path in paths: - path.position_endpoints_on(start, end) - - -class RollingRandolph(PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - self.slide(randy, self.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() - - -class TransitionAwayFromSlide(PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - arrow = Arrow(ORIGIN, 2*RIGHT) - arrows = Mobject(*[ - arrow.copy().shift(vect) - for vect in 3*LEFT, ORIGIN, 3*RIGHT - ]) - arrows.shift(2*SPACE_WIDTH*RIGHT) - self.add(arrows) - - self.add(self.cycloid) - self.slide(randy, self.cycloid) - everything = Mobject(*self.mobjects) - self.play(ApplyMethod( - everything.shift, 4*SPACE_WIDTH*LEFT, - run_time = 2, - rate_func = rush_into - )) - - -class MinimalPotentialEnergy(Scene): - def construct(self): - horiz_radius = 5 - vert_radius = 3 - - vert_axis = NumberLine(numerical_radius = vert_radius) - vert_axis.rotate(np.pi/2) - vert_axis.shift(horiz_radius*LEFT) - horiz_axis = NumberLine( - numerical_radius = 5, - numbers_with_elongated_ticks = [] - ) - axes = Mobject(horiz_axis, vert_axis) - graph = FunctionGraph( - lambda x : 0.4*(x-2)*(x+2)+3, - x_min = -2, - x_max = 2, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - graph.stretch_to_fit_width(2*horiz_radius) - graph.highlight(YELLOW) - min_point = Dot(graph.get_bottom()) - nature_finds = TextMobject("Nature finds this point") - nature_finds.scale(0.5) - nature_finds.highlight(GREEN) - nature_finds.shift(2*RIGHT+3*UP) - arrow = Arrow( - nature_finds.get_bottom(), min_point, - color = GREEN - ) - - side_words_start = TextMobject("Parameter describing") - top_words, last_side_words = [ - map(TextMobject, pair) - for pair in [ - ("Light's travel time", "Potential energy"), - ("path", "mechanical state") - ] - ] - for word in top_words + last_side_words + [side_words_start]: - word.scale(0.7) - side_words_start.next_to(horiz_axis, DOWN) - side_words_start.to_edge(RIGHT) - for words in top_words: - words.next_to(vert_axis, UP) - words.to_edge(LEFT) - for words in last_side_words: - words.next_to(side_words_start, DOWN) - for words in top_words[1], last_side_words[1]: - words.highlight(RED) - - self.add( - axes, top_words[0], side_words_start, - last_side_words[0] - ) - self.play(ShowCreation(graph)) - self.play( - ShimmerIn(nature_finds), - ShowCreation(arrow), - ShowCreation(min_point) - ) - self.dither() - self.play( - FadeOut(top_words[0]), - FadeOut(last_side_words[0]), - GrowFromCenter(top_words[1]), - GrowFromCenter(last_side_words[1]) - ) - self.dither(3) - - - - -class WhatGovernsSpeed(PathSlidingScene): - CONFIG = { - "num_pieces" : 6, - "dither_and_add" : False, - "show_time" : False, - } - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - points = self.cycloid.points - ceiling = points[0, 1] - n = len(points) - broken_points = [ - points[k*n/self.num_pieces:(k+1)*n/self.num_pieces] - for k in range(self.num_pieces) - ] - words = TextMobject(""" - What determines the speed\\\\ - at each point? - """) - words.to_edge(UP) - - self.add(self.cycloid) - sliders, vectors = [], [] - for points in broken_points: - path = Mobject().add_points(points) - vect = points[-1] - points[-2] - magnitude = np.sqrt(ceiling - points[-1, 1]) - vect = magnitude*vect/np.linalg.norm(vect) - slider = self.slide(randy, path, ceiling = ceiling) - vector = Vector(slider.get_center(), vect) - self.add(slider, vector) - sliders.append(slider) - vectors.append(vector) - self.dither() - self.play(ShimmerIn(words)) - self.dither(3) - slider = sliders.pop(1) - vector = vectors.pop(1) - faders = sliders+vectors+[words] - self.play(*map(FadeOut, faders)) - self.remove(*faders) - self.show_geometry(slider, vector) - - def show_geometry(self, slider, vector): - point_a = self.point_a.get_center() - horiz_line = Line(point_a, point_a + 6*RIGHT) - ceil_point = point_a - ceil_point[0] = slider.get_center()[0] - vert_brace = Brace( - Mobject(Point(ceil_point), Point(slider.get_center())), - RIGHT, - buff = 0.5 - ) - vect_brace = Brace(slider) - vect_brace.stretch_to_fit_width(vector.get_length()) - vect_brace.rotate(np.arctan(vector.get_slope())) - vect_brace.center().shift(vector.get_center()) - nudge = 0.2*(DOWN+LEFT) - vect_brace.shift(nudge) - y_mob = TexMobject("y") - y_mob.next_to(vert_brace) - sqrt_y = TexMobject("k\\sqrt{y}") - sqrt_y.scale(0.5) - sqrt_y.shift(vect_brace.get_center()) - sqrt_y.shift(3*nudge) - - self.play(ShowCreation(horiz_line)) - self.play( - GrowFromCenter(vert_brace), - ShimmerIn(y_mob) - ) - self.play( - GrowFromCenter(vect_brace), - ShimmerIn(sqrt_y) - ) - self.dither(3) - self.solve_energy() - - def solve_energy(self): - loss_in_potential = TextMobject("Loss in potential: ") - loss_in_potential.shift(2*UP) - potential = TexMobject("m g y".split()) - potential.next_to(loss_in_potential) - kinetic = TexMobject([ - "\\dfrac{1}{2}","m","v","^2","=" - ]) - kinetic.next_to(potential, LEFT) - nudge = 0.1*UP - kinetic.shift(nudge) - loss_in_potential.shift(nudge) - ms = Mobject(kinetic.split()[1], potential.split()[0]) - two = TexMobject("2") - two.shift(ms.split()[1].get_center()) - half = kinetic.split()[0] - sqrt = TexMobject("\\sqrt{\\phantom{2mg}}") - sqrt.shift(potential.get_center()) - nudge = 0.2*LEFT - sqrt.shift(nudge) - squared = kinetic.split()[3] - equals = kinetic.split()[-1] - new_eq = equals.copy().next_to(kinetic.split()[2]) - - self.play( - Transform( - Point(loss_in_potential.get_left()), - loss_in_potential - ), - *map(GrowFromCenter, potential.split()) - ) - self.dither(2) - self.play( - FadeOut(loss_in_potential), - GrowFromCenter(kinetic) - ) - self.dither(2) - self.play(ApplyMethod(ms.shift, 5*UP)) - self.dither() - self.play(Transform( - half, two, - path_func = counterclockwise_path() - )) - self.dither() - self.play( - Transform( - squared, sqrt, - path_func = clockwise_path() - ), - Transform(equals, new_eq) - ) - self.dither(2) - - - - -class ThetaTInsteadOfXY(Scene): - def construct(self): - cycloid = Cycloid() - index = cycloid.get_num_points()/3 - point = cycloid.points[index] - vect = cycloid.points[index+1]-point - vect /= np.linalg.norm(vect) - vect *= 3 - vect_mob = Vector(point, vect) - dot = Dot(point) - xy = TexMobject("\\big( x(t), y(t) \\big)") - xy.next_to(dot, UP+RIGHT, buff = 0.1) - vert_line = Line(2*DOWN, 2*UP) - vert_line.shift(point) - angle = vect_mob.get_angle() + np.pi/2 - arc = Arc(angle, radius = 1, start_angle = -np.pi/2) - arc.shift(point) - theta = TexMobject("\\theta(t)") - theta.next_to(arc, DOWN, buff = 0.1, aligned_edge = LEFT) - theta.shift(0.2*RIGHT) - - self.play(ShowCreation(cycloid)) - self.play(ShowCreation(dot)) - self.play(ShimmerIn(xy)) - self.dither() - self.play( - FadeOut(xy), - ShowCreation(vect_mob) - ) - self.play( - ShowCreation(arc), - ShowCreation(vert_line), - ShimmerIn(theta) - ) - self.dither() - - -class DefineCurveWithKnob(PathSlidingScene): - def construct(self): - self.knob = Circle(color = BLUE_D) - self.knob.add_line(UP, DOWN) - self.knob.to_corner(UP+RIGHT) - self.knob.shift(0.5*DOWN) - self.last_angle = np.pi/2 - arrow = Vector(ORIGIN, RIGHT) - arrow.next_to(self.knob, LEFT) - words = TextMobject("Turn this knob over time to define the curve") - words.next_to(arrow, LEFT) - self.path = self.get_path() - self.path.shift(1.5*DOWN) - self.path.show() - self.path.highlight(BLACK) - - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - - self.play(ShimmerIn(words)) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(self.knob)) - self.dither() - self.add(self.path) - - self.slide(randy, self.path) - self.dither() - - - def get_path(self): - return Cycloid(end_theta = 2*np.pi) - - def midslide_action(self, point, angle): - d_angle = angle-self.last_angle - self.knob.rotate_in_place(d_angle) - self.last_angle = angle - self.path.highlight(BLUE_D, lambda p : p[0] < point[0]) - - - -class WonkyDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - return ParametricFunction( - lambda t : t*RIGHT + (-0.2*t-np.sin(2*np.pi*t/6))*UP, - start = -7, - end = 10 - ) - - -class SlowDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - return ParametricFunction( - lambda t : t*RIGHT + (np.exp(-(t+2)**2)-0.2*np.exp(t-2)), - start = -4, - end = 4 - ) - - -class BumpyDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - - result = FunctionGraph( - lambda x : 0.05*(x**2)+0.1*np.sin(2*x) - ) - result.rotate(-np.pi/20) - result.scale(0.7) - result.shift(DOWN) - return result - - - - - - - - - - - - - diff --git a/brachistochrone/cycloid.py b/brachistochrone/cycloid.py deleted file mode 100644 index 6cac54c9..00000000 --- a/brachistochrone/cycloid.py +++ /dev/null @@ -1,611 +0,0 @@ -import numpy as np -import itertools as it - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject, Mobject1D -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from topics.geometry import * -from topics.characters import Randolph -from topics.functions import * -from mobject.region import Region -from scene import Scene -from scene.zoomed_scene import ZoomedScene - -from camera import Camera -from brachistochrone.curves import * - -class RollAlongVector(Animation): - CONFIG = { - "rotation_vector" : OUT, - } - def __init__(self, mobject, vector, **kwargs): - radius = mobject.get_width()/2 - radians = np.linalg.norm(vector)/radius - last_alpha = 0 - digest_config(self, kwargs, locals()) - Animation.__init__(self, mobject, **kwargs) - - def update_mobject(self, alpha): - d_alpha = alpha - self.last_alpha - self.last_alpha = alpha - self.mobject.rotate_in_place( - d_alpha*self.radians, - self.rotation_vector - ) - self.mobject.shift(d_alpha*self.vector) - - -class CycloidScene(Scene): - CONFIG = { - "point_a" : 6*LEFT+3*UP, - "radius" : 2, - "end_theta" : 2*np.pi - } - def construct(self): - self.generate_cycloid() - self.generate_circle() - self.generate_ceiling() - - def grow_parts(self): - self.play(*[ - ShowCreation(mob) - for mob in self.circle, self.ceiling - ]) - - def generate_cycloid(self): - self.cycloid = Cycloid( - point_a = self.point_a, - radius = self.radius, - end_theta = self.end_theta - ) - - def generate_circle(self, **kwargs): - self.circle = Circle(radius = self.radius, **kwargs) - self.circle.shift(self.point_a - self.circle.get_top()) - radial_line = Line( - self.circle.get_center(), self.point_a - ) - self.circle.add(radial_line) - - def generate_ceiling(self): - self.ceiling = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT) - self.ceiling.shift(self.cycloid.get_top()[1]*UP) - - def draw_cycloid(self, run_time = 3, *anims, **kwargs): - kwargs["run_time"] = run_time - self.play( - RollAlongVector( - self.circle, - self.cycloid.points[-1]-self.cycloid.points[0], - **kwargs - ), - ShowCreation(self.cycloid, **kwargs), - *anims - ) - - def roll_back(self, run_time = 3, *anims, **kwargs): - kwargs["run_time"] = run_time - self.play( - RollAlongVector( - self.circle, - self.cycloid.points[0]-self.cycloid.points[- 1], - rotation_vector = IN, - **kwargs - ), - ShowCreation( - self.cycloid, - rate_func = lambda t : smooth(1-t), - **kwargs - ), - *anims - ) - self.generate_cycloid() - - -class IntroduceCycloid(CycloidScene): - def construct(self): - CycloidScene.construct(self) - - equation = TexMobject([ - "\\dfrac{\\sin(\\theta)}{\\sqrt{y}}", - "= \\text{constant}" - ]) - sin_sqrt, const = equation.split() - new_eq = equation.copy() - new_eq.to_edge(UP, buff = 1.3) - cycloid_word = TextMobject("Cycloid") - arrow = Arrow(2*UP, cycloid_word) - arrow.reverse_points() - q_mark = TextMobject("?") - - self.play(*map(ShimmerIn, equation.split())) - self.dither() - self.play( - ApplyMethod(equation.shift, 2.2*UP), - ShowCreation(arrow) - ) - q_mark.next_to(sin_sqrt) - self.play(ShimmerIn(cycloid_word)) - self.dither() - self.grow_parts() - self.draw_cycloid() - self.dither() - extra_terms = [const, arrow, cycloid_word] - self.play(*[ - Transform(mob, q_mark) - for mob in extra_terms - ]) - self.remove(*extra_terms) - self.roll_back() - q_marks, arrows = self.get_q_marks_and_arrows(sin_sqrt) - self.draw_cycloid(3, - ShowCreation(q_marks), - ShowCreation(arrows) - ) - self.dither() - - def get_q_marks_and_arrows(self, mob, n_marks = 10): - circle = Circle().replace(mob) - q_marks, arrows = result = [Mobject(), Mobject()] - for x in range(n_marks): - index = (x+0.5)*self.cycloid.get_num_points()/n_marks - q_point = self.cycloid.points[index] - vect = q_point-mob.get_center() - start_point = circle.get_boundary_point(vect) - arrow = Arrow( - start_point, q_point, - color = BLUE_E - ) - - q_marks.add(TextMobject("?").shift(q_point)) - arrows.add(arrow) - for mob in result: - mob.ingest_sub_mobjects() - return result - - -class LeviSolution(CycloidScene): - CONFIG = { - "cycloid_fraction" : 0.25, - } - def construct(self): - CycloidScene.construct(self) - self.add(self.ceiling) - self.generate_points() - methods = [ - self.draw_cycloid, - self.roll_into_position, - self.draw_p_and_c, - self.show_pendulum, - self.show_diameter, - self.show_theta, - self.show_similar_triangles, - self.show_sin_thetas, - self.show_y, - self.rearrange, - ] - for method in methods: - method() - self.dither() - - - def generate_points(self): - index = int(self.cycloid_fraction*self.cycloid.get_num_points()) - p_point = self.cycloid.points[index] - p_dot = Dot(p_point) - p_label = TexMobject("P") - p_label.next_to(p_dot, DOWN+LEFT) - c_point = self.point_a + self.cycloid_fraction*self.radius*2*np.pi*RIGHT - c_dot = Dot(c_point) - c_label = TexMobject("C") - c_label.next_to(c_dot, UP) - - digest_locals(self) - - def roll_into_position(self): - self.play(RollAlongVector( - self.circle, - (1-self.cycloid_fraction)*self.radius*2*np.pi*LEFT, - rotation_vector = IN, - run_time = 2 - )) - - def draw_p_and_c(self): - radial_line = self.circle.sub_mobjects[0] ##Hacky - self.play(Transform(radial_line, self.p_dot)) - self.remove(radial_line) - self.add(self.p_dot) - self.play(ShimmerIn(self.p_label)) - self.dither() - self.play(Transform(self.ceiling.copy(), self.c_dot)) - self.play(ShimmerIn(self.c_label)) - - def show_pendulum(self, arc_angle = np.pi, arc_color = GREEN): - words = TextMobject(": Instantaneous center of rotation") - words.next_to(self.c_label) - line = Line(self.p_point, self.c_point) - line_angle = line.get_angle()+np.pi - line_length = line.get_length() - line.add(self.p_dot.copy()) - line.get_center = lambda : self.c_point - tangent_line = Line(3*LEFT, 3*RIGHT) - tangent_line.rotate(line_angle-np.pi/2) - tangent_line.shift(self.p_point) - tangent_line.highlight(arc_color) - right_angle_symbol = Mobject( - Line(UP, UP+RIGHT), - Line(UP+RIGHT, RIGHT) - ) - right_angle_symbol.scale(0.3) - right_angle_symbol.rotate(tangent_line.get_angle()+np.pi) - right_angle_symbol.shift(self.p_point) - - self.play(ShowCreation(line)) - self.play(ShimmerIn(words)) - self.dither() - pairs = [ - (line_angle, arc_angle/2), - (line_angle+arc_angle/2, -arc_angle), - (line_angle-arc_angle/2, arc_angle/2), - ] - arcs = [] - for start, angle in pairs: - arc = Arc( - angle = angle, - radius = line_length, - start_angle = start, - color = GREEN - ) - arc.shift(self.c_point) - self.play( - ShowCreation(arc), - ApplyMethod( - line.rotate_in_place, - angle, - path_func = path_along_arc(angle) - ), - run_time = 2 - ) - arcs.append(arc) - self.dither() - self.play(Transform(arcs[1], tangent_line)) - self.add(tangent_line) - self.play(ShowCreation(right_angle_symbol)) - self.dither() - - self.tangent_line = tangent_line - self.right_angle_symbol = right_angle_symbol - self.pc_line = line - self.remove(words, *arcs) - - def show_diameter(self): - exceptions = [ - self.circle, - self.tangent_line, - self.pc_line, - self.right_angle_symbol - ] - everything = set(self.mobjects).difference(exceptions) - everything_copy = Mobject(*everything).copy() - light_everything = everything_copy.copy() - dark_everything = everything_copy.copy() - dark_everything.fade(0.8) - bottom_point = np.array(self.c_point) - bottom_point += 2*self.radius*DOWN - diameter = Line(bottom_point, self.c_point) - brace = Brace(diameter, RIGHT) - diameter_word = TextMobject("Diameter") - d_mob = TexMobject("D") - diameter_word.next_to(brace) - d_mob.next_to(diameter) - - self.remove(*everything) - self.play(Transform(everything_copy, dark_everything)) - self.dither() - self.play(ShowCreation(diameter)) - self.play(GrowFromCenter(brace)) - self.play(ShimmerIn(diameter_word)) - self.dither() - self.play(*[ - Transform(mob, d_mob) - for mob in brace, diameter_word - ]) - self.remove(brace, diameter_word) - self.add(d_mob) - self.play(Transform(everything_copy, light_everything)) - self.remove(everything_copy) - self.add(*everything) - - self.d_mob = d_mob - self.bottom_point = bottom_point - - def show_theta(self, radius = 1): - arc = Arc( - angle = self.tangent_line.get_angle()-np.pi/2, - radius = radius, - start_angle = np.pi/2 - ) - - theta = TexMobject("\\theta") - theta.shift(1.5*arc.get_center()) - Mobject(arc, theta).shift(self.bottom_point) - - self.play( - ShowCreation(arc), - ShimmerIn(theta) - ) - self.arc = arc - self.theta = theta - - def show_similar_triangles(self): - y_point = np.array(self.p_point) - y_point[1] = self.point_a[1] - new_arc = Arc( - angle = self.tangent_line.get_angle()-np.pi/2, - radius = 0.5, - start_angle = np.pi - ) - new_arc.shift(self.c_point) - new_theta = self.theta.copy() - new_theta.next_to(new_arc, LEFT) - new_theta.shift(0.1*DOWN) - kwargs = { - "point_thickness" : 2*DEFAULT_POINT_THICKNESS, - } - triangle1 = Polygon( - self.p_point, self.c_point, self.bottom_point, - color = MAROON, - **kwargs - ) - triangle2 = Polygon( - y_point, self.p_point, self.c_point, - color = WHITE, - **kwargs - ) - y_line = Line(self.p_point, y_point) - - self.play( - Transform(self.arc.copy(), new_arc), - Transform(self.theta.copy(), new_theta), - run_time = 3 - ) - self.dither() - self.play(FadeIn(triangle1)) - self.dither() - self.play(Transform(triangle1, triangle2)) - self.play(ApplyMethod(triangle1.highlight, MAROON)) - self.dither() - self.remove(triangle1) - self.add(y_line) - - self.y_line = y_line - - def show_sin_thetas(self): - pc = Line(self.p_point, self.c_point) - mob = Mobject(self.theta, self.d_mob).copy() - mob.ingest_sub_mobjects() - triplets = [ - (pc, "D\\sin(\\theta)", 0.5), - (self.y_line, "D\\sin^2(\\theta)", 0.7), - ] - for line, tex, scale in triplets: - trig_mob = TexMobject(tex) - trig_mob.scale_to_fit_width( - scale*line.get_length() - ) - trig_mob.shift(-1.2*trig_mob.get_top()) - trig_mob.rotate(line.get_angle()) - trig_mob.shift(line.get_center()) - if line is self.y_line: - trig_mob.shift(0.1*UP) - - self.play(Transform(mob, trig_mob)) - self.add(trig_mob) - self.dither() - - self.remove(mob) - self.d_sin_squared_theta = trig_mob - - - def show_y(self): - y_equals = TexMobject(["y", "="]) - y_equals.shift(2*UP) - y_expression = TexMobject([ - "D ", "\\sin", "^2", "(\\theta)" - ]) - y_expression.next_to(y_equals) - y_expression.shift(0.05*UP+0.1*RIGHT) - temp_expr = self.d_sin_squared_theta.copy() - temp_expr.rotate(-np.pi/2) - temp_expr.replace(y_expression) - y_mob = TexMobject("y") - y_mob.next_to(self.y_line, RIGHT) - y_mob.shift(0.2*UP) - - self.play( - Transform(self.d_sin_squared_theta, temp_expr), - ShimmerIn(y_mob), - ShowCreation(y_equals) - ) - self.remove(self.d_sin_squared_theta) - self.add(y_expression) - - self.y_equals = y_equals - self.y_expression = y_expression - - def rearrange(self): - sqrt_nudge = 0.2*LEFT - y, equals = self.y_equals.split() - d, sin, squared, theta = self.y_expression.split() - y_sqrt = TexMobject("\\sqrt{\\phantom{y}}") - d_sqrt = y_sqrt.copy() - y_sqrt.shift(y.get_center()+sqrt_nudge) - d_sqrt.shift(d.get_center()+sqrt_nudge) - - self.play( - ShimmerIn(y_sqrt), - ShimmerIn(d_sqrt), - ApplyMethod(squared.shift, 4*UP), - ApplyMethod(theta.shift, 1.5* squared.get_width()*LEFT) - ) - self.dither() - y_sqrt.add(y) - d_sqrt.add(d) - sin.add(theta) - - sin_over = TexMobject("\\dfrac{\\phantom{\\sin(\\theta)}}{\\quad}") - sin_over.next_to(sin, DOWN, 0.15) - new_eq = equals.copy() - new_eq.next_to(sin_over, LEFT) - one_over = TexMobject("\\dfrac{1}{\\quad}") - one_over.next_to(new_eq, LEFT) - one_over.shift( - (sin_over.get_bottom()[1]-one_over.get_bottom()[1])*UP - ) - - self.play( - Transform(equals, new_eq), - ShimmerIn(sin_over), - ShimmerIn(one_over), - ApplyMethod( - d_sqrt.next_to, one_over, DOWN, - path_func = path_along_arc(-np.pi) - ), - ApplyMethod( - y_sqrt.next_to, sin_over, DOWN, - path_func = path_along_arc(-np.pi) - ), - run_time = 2 - ) - self.dither() - - brace = Brace(d_sqrt, DOWN) - constant = TextMobject("Constant") - constant.next_to(brace, DOWN) - - self.play( - GrowFromCenter(brace), - ShimmerIn(constant) - ) - - - - -class EquationsForCycloid(CycloidScene): - def construct(self): - CycloidScene.construct(self) - equations = TexMobject([ - "x(t) = Rt - R\\sin(t)", - "y(t) = -R + R\\cos(t)" - ]) - top, bottom = equations.split() - bottom.next_to(top, DOWN) - equations.center() - equations.to_edge(UP, buff = 1.3) - - self.play(ShimmerIn(equations)) - self.grow_parts() - self.draw_cycloid(rate_func = None, run_time = 5) - self.dither() - -class SlidingObject(CycloidScene, PathSlidingScene): - CONFIG = { - "show_time" : False, - "dither_and_add" : False - } - - args_list = [(True,), (False,)] - - @staticmethod - def args_to_string(with_words): - return "WithWords" if with_words else "WithoutWords" - - @staticmethod - def string_to_args(string): - return string == "WithWords" - - def construct(self, with_words): - CycloidScene.construct(self) - - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - central_randy = randy.copy() - start_randy = self.adjust_mobject_to_index( - randy.copy(), 1, self.cycloid.points - ) - - if with_words: - words1 = TextMobject("Trajectory due to gravity") - arrow = TexMobject("\\leftrightarrow") - words2 = TextMobject("Trajectory due \\emph{constantly} rotating wheel") - words1.next_to(arrow, LEFT) - words2.next_to(arrow, RIGHT) - words = Mobject(words1, arrow, words2) - words.scale_to_fit_width(2*SPACE_WIDTH-1) - words.to_edge(UP, buff = 0.2) - words.to_edge(LEFT) - - self.play(ShowCreation(self.cycloid.copy())) - self.slide(randy, self.cycloid) - self.add(self.slider) - self.dither() - self.grow_parts() - self.draw_cycloid() - self.dither() - self.play(Transform(self.slider, start_randy)) - self.dither() - self.roll_back() - self.dither() - if with_words: - self.play(*map(ShimmerIn, [words1, arrow, words2])) - self.dither() - self.remove(self.circle) - start_time = len(self.frames)*self.frame_duration - self.remove(self.slider) - self.slide(central_randy, self.cycloid) - end_time = len(self.frames)*self.frame_duration - self.play_over_time_range( - start_time, - end_time, - RollAlongVector( - self.circle, - self.cycloid.points[-1]-self.cycloid.points[0], - run_time = end_time-start_time, - rate_func = None - ) - ) - self.add(self.circle, self.slider) - self.dither() - - - -class RotateWheel(CycloidScene): - def construct(self): - CycloidScene.construct(self) - self.circle.center() - - self.play(Rotating( - self.circle, - axis = OUT, - run_time = 5, - rate_func = smooth - )) - - - - - - - - - - diff --git a/brachistochrone/drawing_images.py b/brachistochrone/drawing_images.py deleted file mode 100644 index 0173a5bb..00000000 --- a/brachistochrone/drawing_images.py +++ /dev/null @@ -1,429 +0,0 @@ -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 -from mobject import Mobject -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from mobject.tex_mobject import TextMobject, TexMobject - -from animation.transform import \ - Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ - 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, ThoughtBubble -from topics.functions import ParametricFunction -from topics.number_line import NumberPlane -from mobject.region import Region, region_from_polygon_vertices -from scene import Scene - - -DEFAULT_GAUSS_BLUR_CONFIG = { - "ksize" : (5, 5), - "sigmaX" : 6, - "sigmaY" : 6, -} - -DEFAULT_CANNY_CONFIG = { - "threshold1" : 50, - "threshold2" : 100, -} - -DEFAULT_BLUR_RADIUS = 0.5 -DEFAULT_CONNECTED_COMPONENT_THRESHOLD = 25 - - -def reverse_colors(nparray): - return nparray[:,:,[2, 1, 0]] - -def show(nparray): - Image.fromarray(reverse_colors(nparray)).show() - - -def thicken(nparray): - height, width = nparray.shape - nparray = nparray.reshape((height, width, 1)) - return np.repeat(nparray, 3, 2) - -def sort_by_color(mob): - indices = np.argsort(np.apply_along_axis( - lambda p : -np.linalg.norm(p), - 1, - mob.rgbs - )) - mob.rgbs = mob.rgbs[indices] - mob.points = mob.points[indices] - - - -def get_image_array(name): - image_files = os.listdir(IMAGE_DIR) - possibilities = filter(lambda s : s.startswith(name), image_files) - for possibility in possibilities: - try: - path = os.path.join(IMAGE_DIR, possibility) - image = Image.open(path) - image = image.convert('RGB') - return np.array(image) - except: - pass - raise Exception("Image for %s not found"%name) - -def get_edges(image_array): - blurred = cv2.GaussianBlur( - image_array, - **DEFAULT_GAUSS_BLUR_CONFIG - ) - edges = cv2.Canny( - blurred, - **DEFAULT_CANNY_CONFIG - ) - return edges - -def nearest_neighbor_align(mobject1, mobject2): - distance_matrix = cdist(mobject1.points, mobject2.points) - closest_point_indices = np.apply_along_axis( - np.argmin, 0, distance_matrix - ) - new_mob1 = Mobject() - new_mob2 = Mobject() - for n in range(mobject1.get_num_points()): - indices = (closest_point_indices == n) - new_mob1.add_points( - [mobject1.points[n]]*sum(indices) - ) - new_mob2.add_points( - mobject2.points[indices], - rgbs = mobject2.rgbs[indices] - ) - return new_mob1, new_mob2 - -def get_connected_components(image_array, - blur_radius = DEFAULT_BLUR_RADIUS, - threshold = DEFAULT_CONNECTED_COMPONENT_THRESHOLD): - blurred_image = ndimage.gaussian_filter(image_array, blur_radius) - labels, component_count = ndimage.label(blurred_image > threshold) - return [ - image_array * (labels == count) - for count in range(1, component_count+1) - ] - -def color_region(bw_region, colored_image): - return thicken(bw_region > 0) * colored_image - - -class TracePicture(Scene): - args_list = [ - ("Newton",), - ("Mark_Levi",), - ("Steven_Strogatz",), - ("Pierre_de_Fermat",), - ("Galileo_Galilei",), - ("Jacob_Bernoulli",), - ("Johann_Bernoulli2",), - ("Old_Newton",) - ] - - @staticmethod - def args_to_string(name): - return name - - @staticmethod - def string_to_args(name): - return name - - def construct(self, name): - run_time = 20 - scale_factor = 0.8 - image_array = get_image_array(name) - edge_mobject = self.get_edge_mobject(image_array) - full_picture = MobjectFromPixelArray(image_array) - for mob in edge_mobject, full_picture: - # mob.point_thickness = 4 - mob.scale(scale_factor) - mob.show() - - self.play( - DelayByOrder(FadeIn( - full_picture, - run_time = run_time, - rate_func = squish_rate_func(smooth, 0.7, 1) - )), - ShowCreation( - edge_mobject, - run_time = run_time, - rate_func = None - ) - ) - self.remove(edge_mobject) - self.dither() - - - def get_edge_mobject(self, image_array): - edged_image = get_edges(image_array) - individual_edges = get_connected_components(edged_image) - colored_edges = [ - color_region(edge, image_array) - for edge in individual_edges - ] - colored_edge_mobject_list = [ - MobjectFromPixelArray(colored_edge) - for colored_edge in colored_edges - ] - random.shuffle(colored_edge_mobject_list) - edge_mobject = Mobject(*colored_edge_mobject_list) - edge_mobject.ingest_sub_mobjects() - return edge_mobject - - - -class JohannThinksHeIsBetter(Scene): - def construct(self): - names = [ - "Johann_Bernoulli2", - "Jacob_Bernoulli", - "Gottfried_Wilhelm_von_Leibniz", - "Newton" - ] - 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)) - upper_point = Point(comparitive_johann.get_corner(UP+RIGHT)) - lightbulb = ImageMobject("Lightbulb", invert = False) - lightbulb.scale(0.1) - lightbulb.sort_points(np.linalg.norm) - lightbulb.next_to(upper_point, 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, upper_point - ))) - self.play( - FadeIn(guy), - ShimmerIn(guy.name_mob) - ) - self.dither(3) - self.remove(guy.name_mob) - weakling = guy - self.play(FadeOut(weakling), FadeOut(greater_than)) - self.play(ShowCreation(lightbulb)) - self.dither() - self.play(FadeOut(comparitive_johann), FadeOut(lightbulb)) - self.play(ApplyMethod( - Mobject(johann, bubble).scale, 10, - run_time = 3 - )) - - -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() - - -class MathematiciansOfEurope(Scene): - def construct(self): - europe = ImageMobject("Europe", use_cache = False) - self.add(europe) - self.freeze_background() - - mathematicians = [ - ("Newton", [-1.75, -0.75, 0]), - ("Jacob_Bernoulli",[-0.75, -1.75, 0]), - ("Ehrenfried_von_Tschirnhaus",[0.5, -0.5, 0]), - ("Gottfried_Wilhelm_von_Leibniz",[0.2, -1.75, 0]), - ("Guillaume_de_L'Hopital", [-1.75, -1.25, 0]), - ] - - for name, point in mathematicians: - man = ImageMobject(name, invert = False) - if name == "Newton": - name = "Isaac_Newton" - name_mob = TextMobject(name.replace("_", " ")) - name_mob.to_corner(UP+LEFT, buff=0.75) - self.add(name_mob) - man.scale_to_fit_height(4) - mobject = Point(man.get_corner(UP+LEFT)) - self.play(Transform(mobject, man)) - man.scale(0.2) - man.shift(point) - self.play(Transform(mobject, man)) - self.remove(name_mob) - -class OldNewtonIsDispleased(Scene): - def construct(self): - old_newton = ImageMobject("Old_Newton", invert = False) - old_newton.scale(0.8) - self.add(old_newton) - self.freeze_background() - - words = TextMobject("Note the displeasure") - words.to_corner(UP+RIGHT) - face_point = 1.8*UP+0.5*LEFT - arrow = Arrow(words.get_bottom(), face_point) - - - self.play(ShimmerIn(words)) - self.play(ShowCreation(arrow)) - self.dither() - - -class NewtonConsideredEveryoneBeneathHim(Scene): - def construct(self): - mathematicians = [ - ImageMobject(name, invert = False) - for name in [ - "Old_Newton", - "Johann_Bernoulli2", - "Jacob_Bernoulli", - "Ehrenfried_von_Tschirnhaus", - "Gottfried_Wilhelm_von_Leibniz", - "Guillaume_de_L'Hopital", - ] - ] - newton = mathematicians.pop(0) - newton.scale(0.8) - new_newton = newton.copy() - new_newton.scale_to_fit_height(3) - new_newton.to_edge(UP) - for man in mathematicians: - man.scale_to_fit_width(1.7) - johann = mathematicians.pop(0) - johann.next_to(new_newton, DOWN) - last_left, last_right = johann, johann - for man, count in zip(mathematicians, it.count()): - if count%2 == 0: - man.next_to(last_left, LEFT) - last_left = man - else: - man.next_to(last_right, RIGHT) - last_right = man - - self.play( - Transform(newton, new_newton), - GrowFromCenter(johann) - ) - self.dither() - self.play(FadeIn(Mobject(*mathematicians))) - self.dither() - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/brachistochrone/graveyard.py b/brachistochrone/graveyard.py deleted file mode 100644 index 97cd9143..00000000 --- a/brachistochrone/graveyard.py +++ /dev/null @@ -1,344 +0,0 @@ -import numpy as np -import itertools as it - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject, Mobject1D -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import * -from topics.characters import Randolph, Mathematician -from topics.functions import * -from topics.number_line import * -from mobject.region import Region, region_from_polygon_vertices -from scene import Scene -from scene.zoomed_scene import ZoomedScene - -from brachistochrone.curves import Cycloid - -class MultilayeredGlass(PhotonScene, ZoomedScene): - CONFIG = { - "num_discrete_layers" : 5, - "num_variables" : 3, - "top_color" : BLUE_E, - "bottom_color" : BLUE_A, - "zoomed_canvas_space_shape" : (5, 5), - "square_color" : GREEN_B, - } - def construct(self): - self.cycloid = Cycloid(end_theta = np.pi) - self.cycloid.highlight(YELLOW) - self.top = self.cycloid.get_top()[1] - self.bottom = self.cycloid.get_bottom()[1]-1 - self.generate_layers() - self.generate_discrete_path() - photon_run = self.photon_run_along_path( - self.discrete_path, - run_time = 1, - rate_func = rush_into - ) - - - - self.continuous_to_smooth() - self.add(*self.layers) - self.show_layer_variables() - self.play(photon_run) - self.play(ShowCreation(self.discrete_path)) - self.isolate_bend_points() - self.clear() - self.add(*self.layers) - self.show_main_equation() - self.ask_continuous_question() - - def continuous_to_smooth(self): - self.add(*self.layers) - continuous = self.get_continuous_background() - self.add(continuous) - self.dither() - self.play(ShowCreation( - continuous, - rate_func = lambda t : smooth(1-t) - )) - self.remove(continuous) - self.dither() - - def get_continuous_background(self): - glass = FilledRectangle( - height = self.top-self.bottom, - width = 2*SPACE_WIDTH, - ) - glass.sort_points(lambda p : -p[1]) - glass.shift((self.top-glass.get_top()[1])*UP) - glass.gradient_highlight(self.top_color, self.bottom_color) - 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_layers(self): - self.generate_layer_info() - def create_region(top, color): - return Region( - lambda x, y : (y < top) & (y > top-self.layer_thickness), - color = color - ) - self.layers = [ - create_region(top, color) - for top, color in zip(self.layer_tops, self.layer_colors) - ] - - - def generate_discrete_path(self): - points = self.cycloid.points - tops = list(self.layer_tops) - tops.append(tops[-1]-self.layer_thickness) - indices = [ - np.argmin(np.abs(points[:, 1]-top)) - for top in tops - ] - self.bend_points = points[indices[1:-1]] - self.path_angles = [] - self.discrete_path = Mobject1D( - color = YELLOW, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - for start, end in zip(indices, indices[1:]): - start_point, end_point = points[start], points[end] - self.discrete_path.add_line( - start_point, end_point - ) - self.path_angles.append( - angle_of_vector(start_point-end_point)-np.pi/2 - ) - self.discrete_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): - 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): - arc_radius = 0.1 - self.activate_zooming() - little_square = self.get_zoomed_camera_mobject() - - for index in range(3): - bend_point = self.bend_points[index] - line = Line( - bend_point+DOWN, - bend_point+UP, - color = WHITE, - density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D - ) - angle_arcs = [] - for i, rotation in [(index, np.pi/2), (index+1, -np.pi/2)]: - arc = Arc(angle = self.path_angles[i]) - arc.scale(arc_radius) - arc.rotate(rotation) - arc.shift(bend_point) - angle_arcs.append(arc) - thetas = [] - for i in [index+1, index+2]: - theta = TexMobject("\\theta_%d"%i) - theta.scale(0.5/self.zoom_factor) - vert = UP if i == index+1 else DOWN - horiz = rotate_vector(vert, np.pi/2) - theta.next_to( - Point(bend_point), - horiz, - buff = 0.01 - ) - theta.shift(1.5*arc_radius*vert) - thetas.append(theta) - figure_marks = [line] + angle_arcs + thetas - - self.play(ApplyMethod( - little_square.shift, - bend_point - little_square.get_center(), - run_time = 2 - )) - self.play(*map(ShowCreation, figure_marks)) - self.dither() - equation_frame = little_square.copy() - equation_frame.scale(0.5) - equation_frame.shift( - little_square.get_corner(UP+RIGHT) - \ - equation_frame.get_corner(UP+RIGHT) - ) - equation_frame.scale_in_place(0.9) - self.show_snells(index+1, equation_frame) - self.remove(*figure_marks) - self.disactivate_zooming() - - def show_snells(self, index, frame): - left_text, right_text = [ - "\\dfrac{\\sin(\\theta_%d)}{\\phantom{\\sqrt{y_1}}}"%x - for x in index, index+1 - ] - left, equals, right = TexMobject( - [left_text, "=", right_text] - ).split() - vs = [] - sqrt_ys = [] - for x, numerator in [(index, left), (index+1, right)]: - v, sqrt_y = [ - TexMobject( - text, size = "\\Large" - ).next_to(numerator, DOWN) - for text in "v_%d"%x, "\\sqrt{y_%d}"%x - ] - vs.append(v) - sqrt_ys.append(sqrt_y) - start, end = [ - Mobject( - left.copy(), mobs[0], equals.copy(), right.copy(), mobs[1] - ).replace(frame) - for mobs in vs, sqrt_ys - ] - - self.add(start) - self.dither(2) - self.play(Transform( - start, end, - path_func = counterclockwise_path() - )) - self.dither(2) - self.remove(start, end) - - def show_main_equation(self): - self.equation = TexMobject(""" - \\dfrac{\\sin(\\theta)}{\\sqrt{y}} = - \\text{constant} - """) - self.equation.shift(LEFT) - self.equation.shift( - (self.layer_tops[0]-self.equation.get_top())*UP - ) - self.add(self.equation) - self.dither() - - def ask_continuous_question(self): - continuous = self.get_continuous_background() - line = Line( - UP, DOWN, - density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D - ) - theta = TexMobject("\\theta") - theta.scale(0.5/self.zoom_factor) - - self.play( - ShowCreation(continuous), - Animation(self.equation) - ) - self.remove(*self.layers) - self.play(ShowCreation(self.cycloid)) - self.activate_zooming() - little_square = self.get_zoomed_camera_mobject() - - self.add(line) - indices = np.arange( - 0, self.cycloid.get_num_points()-1, 10 - ) - for index in indices: - point = self.cycloid.points[index] - next_point = self.cycloid.points[index+1] - angle = angle_of_vector(point - next_point) - for mob in little_square, line: - mob.shift(point - mob.get_center()) - arc = Arc(angle-np.pi/2, start_angle = np.pi/2) - arc.scale(0.1) - arc.shift(point) - self.add(arc) - if angle > np.pi/2 + np.pi/6: - vect_angle = interpolate(np.pi/2, angle, 0.5) - vect = rotate_vector(RIGHT, vect_angle) - theta.center() - theta.shift(point) - theta.shift(0.15*vect) - self.add(theta) - self.dither(self.frame_duration) - self.remove(arc) \ No newline at end of file diff --git a/brachistochrone/light.py b/brachistochrone/light.py deleted file mode 100644 index 68ac91de..00000000 --- a/brachistochrone/light.py +++ /dev/null @@ -1,943 +0,0 @@ -import numpy as np -import itertools as it - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject, Mobject1D -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import * -from topics.characters import Randolph, Mathematician -from topics.functions import * -from topics.number_line import * -from mobject.region import Region, region_from_polygon_vertices -from scene import Scene -from scene.zoomed_scene import ZoomedScene - -from brachistochrone.curves import \ - Cycloid, PathSlidingScene, RANDY_SCALE_VAL, TryManyPaths - - -class Lens(Arc): - CONFIG = { - "radius" : 2, - "angle" : np.pi/2, - "color" : BLUE_B, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - Arc.__init__(self, self.angle, **kwargs) - - def generate_points(self): - Arc.generate_points(self) - self.rotate(-np.pi/4) - self.shift(-self.get_left()) - self.add_points(self.copy().rotate(np.pi).points) - - - -class PhotonScene(Scene): - def wavify(self, mobject): - result = mobject.copy() - result.ingest_sub_mobjects() - tangent_vectors = result.points[1:]-result.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.points[1:] += nudges - return result - - - def photon_run_along_path(self, path, color = YELLOW, **kwargs): - if "rate_func" not in kwargs: - kwargs["rate_func"] = None - photon = self.wavify(path) - photon.highlight(color) - return ShowPassingFlash(photon, **kwargs) - - -class SimplePhoton(PhotonScene): - def construct(self): - text = TextMobject("Light") - text.to_edge(UP) - self.play(ShimmerIn(text)) - self.play(self.photon_run_along_path( - Cycloid(), rate_func = None - )) - self.dither() - - -class MultipathPhotonScene(PhotonScene): - CONFIG = { - "num_paths" : 5 - } - def run_along_paths(self, **kwargs): - paths = self.get_paths() - colors = Color(YELLOW).range_to(WHITE, len(paths)) - for path, color in zip(paths, colors): - path.highlight(color) - photon_runs = [ - self.photon_run_along_path(path) - for path in paths - ] - for photon_run, path in zip(photon_runs, paths): - self.play( - photon_run, - ShowCreation( - path, - rate_func = lambda t : 0.9*smooth(t) - ), - **kwargs - ) - self.dither() - - def generate_paths(self): - raise Exception("Not Implemented") - - -class PhotonThroughLens(MultipathPhotonScene): - def construct(self): - self.lens = Lens() - self.add(self.lens) - self.run_along_paths() - - - def get_paths(self): - interval_values = np.arange(self.num_paths).astype('float') - interval_values /= (self.num_paths-1.) - first_contact = [ - self.lens.point_from_proportion(0.4*v+0.55) - for v in reversed(interval_values) - ] - second_contact = [ - self.lens.point_from_proportion(0.3*v + 0.1) - for v in interval_values - ] - focal_point = 2*RIGHT - return [ - Mobject( - Line(SPACE_WIDTH*LEFT + fc[1]*UP, fc), - Line(fc, sc), - Line(sc, focal_point), - Line(focal_point, 6*focal_point-5*sc) - ).ingest_sub_mobjects() - for fc, sc in zip(first_contact, second_contact) - ] - -class TransitionToOptics(PhotonThroughLens): - def construct(self): - optics = TextMobject("Optics") - optics.to_edge(UP) - self.add(optics) - self.has_started = False - PhotonThroughLens.construct(self) - - def play(self, *args, **kwargs): - if not self.has_started: - self.has_started = True - everything = Mobject(*self.mobjects) - vect = 2*SPACE_WIDTH*RIGHT - everything.shift(vect) - self.play(ApplyMethod( - everything.shift, -vect, - rate_func = rush_from - )) - Scene.play(self, *args, **kwargs) - - -class PhotonOffMirror(MultipathPhotonScene): - def construct(self): - self.mirror = Line(*SPACE_HEIGHT*np.array([DOWN, UP])) - self.mirror.highlight(GREY) - self.add(self.mirror) - self.run_along_paths() - - def get_paths(self): - interval_values = np.arange(self.num_paths).astype('float') - interval_values /= (self.num_paths-1) - anchor_points = [ - self.mirror.point_from_proportion(0.6*v+0.3) - for v in interval_values - ] - start_point = 5*LEFT+3*UP - end_points = [] - for point in anchor_points: - vect = start_point-point - vect[1] *= -1 - end_points.append(point+2*vect) - return [ - Mobject( - Line(start_point, anchor_point), - Line(anchor_point, end_point) - ).ingest_sub_mobjects() - for anchor_point, end_point in zip(anchor_points, end_points) - ] - -class PhotonsInWater(MultipathPhotonScene): - def construct(self): - water = Region(lambda x, y : y < 0, color = BLUE_E) - self.add(water) - self.run_along_paths() - - def get_paths(self): - x, y = -3, 3 - start_point = x*RIGHT + y*UP - angles = np.arange(np.pi/18, np.pi/3, np.pi/18) - midpoints = y*np.arctan(angles) - end_points = midpoints + SPACE_HEIGHT*np.arctan(2*angles) - return [ - Mobject( - Line(start_point, [midpoint, 0, 0]), - Line([midpoint, 0, 0], [end_point, -SPACE_HEIGHT, 0]) - ).ingest_sub_mobjects() - for midpoint, end_point in zip(midpoints, end_points) - ] - - -class ShowMultiplePathsScene(PhotonScene): - def construct(self): - text = TextMobject("Which path minimizes travel time?") - text.to_edge(UP) - self.generate_start_and_end_points() - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - paths = self.get_paths() - - for point, letter in [(point_a, A), (point_b, B)]: - self.play( - ShowCreation(point), - ShimmerIn(letter) - ) - self.play(ShimmerIn(text)) - curr_path = paths[0].copy() - curr_path_copy = curr_path.copy().ingest_sub_mobjects() - self.play( - self.photon_run_along_path(curr_path), - ShowCreation(curr_path_copy, rate_func = rush_into) - ) - self.remove(curr_path_copy) - for path in paths[1:] + [paths[0]]: - self.play(Transform(curr_path, path, run_time = 4)) - self.dither() - self.path = curr_path.ingest_sub_mobjects() - - def generate_start_and_end_points(self): - raise Exception("Not Implemented") - - def get_paths(self): - raise Exception("Not implemented") - - -class ShowMultiplePathsThroughLens(ShowMultiplePathsScene): - def construct(self): - self.lens = Lens() - self.add(self.lens) - ShowMultiplePathsScene.construct(self) - - def generate_start_and_end_points(self): - self.start_point = 3*LEFT + UP - self.end_point = 2*RIGHT - - def get_paths(self): - alphas = [0.25, 0.4, 0.58, 0.75] - lower_right, upper_right, upper_left, lower_left = map( - self.lens.point_from_proportion, alphas - ) - return [ - Mobject( - Line(self.start_point, a), - Line(a, b), - Line(b, self.end_point) - ).highlight(color) - for (a, b), color in zip( - [ - (upper_left, upper_right), - (upper_left, lower_right), - (lower_left, lower_right), - (lower_left, upper_right), - ], - Color(YELLOW).range_to(WHITE, 4) - ) - ] - - -class ShowMultiplePathsOffMirror(ShowMultiplePathsScene): - def construct(self): - mirror = Line(*SPACE_HEIGHT*np.array([DOWN, UP])) - mirror.highlight(GREY) - self.add(mirror) - ShowMultiplePathsScene.construct(self) - - def generate_start_and_end_points(self): - self.start_point = 4*LEFT + 2*UP - self.end_point = 4*LEFT + 2*DOWN - - def get_paths(self): - return [ - Mobject( - Line(self.start_point, midpoint), - Line(midpoint, self.end_point) - ).highlight(color) - for midpoint, color in zip( - [2*UP, 2*DOWN], - Color(YELLOW).range_to(WHITE, 2) - ) - ] - - -class ShowMultiplePathsInWater(ShowMultiplePathsScene): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - straight = Line(self.start_point, self.end_point) - slow = TextMobject("Slow") - slow.rotate(np.arctan(straight.get_slope())) - slow.shift(straight.points[int(0.7*straight.get_num_points())]) - slow.shift(0.5*DOWN) - too_long = TextMobject("Too long") - too_long.shift(UP) - air = TextMobject("Air").shift(2*UP) - water = TextMobject("Water").shift(2*DOWN) - - self.add(glass) - self.play(GrowFromCenter(air)) - self.play(GrowFromCenter(water)) - self.dither() - self.remove(air, water) - ShowMultiplePathsScene.construct(self) - self.play( - Transform(self.path, straight) - ) - self.dither() - self.play(GrowFromCenter(slow)) - self.dither() - self.remove(slow) - self.leftmost.ingest_sub_mobjects() - self.play(Transform(self.path, self.leftmost, run_time = 3)) - self.dither() - self.play(ShimmerIn(too_long)) - self.dither() - - - def generate_start_and_end_points(self): - self.start_point = 3*LEFT + 2*UP - self.end_point = 3*RIGHT + 2*DOWN - - def get_paths(self): - self.leftmost, self.rightmost = result = [ - Mobject( - Line(self.start_point, midpoint), - Line(midpoint, self.end_point) - ).highlight(color) - for midpoint, color in zip( - [3*LEFT, 3*RIGHT], - Color(YELLOW).range_to(WHITE, 2) - ) - ] - return result - - -class StraightLinesFastestInConstantMedium(PhotonScene): - def construct(self): - kwargs = {"size" : "\\Large"} - left = TextMobject("Speed of light is constant", **kwargs) - arrow = TexMobject("\\Rightarrow", **kwargs) - right = TextMobject("Staight path is fastest", **kwargs) - left.next_to(arrow, LEFT) - right.next_to(arrow, RIGHT) - squaggle, line = self.get_paths() - - self.play(*map(ShimmerIn, [left, arrow, right])) - self.play(ShowCreation(squaggle)) - self.play(self.photon_run_along_path( - squaggle, run_time = 2, rate_func = None - )) - self.play(Transform( - squaggle, line, - path_func = path_along_arc(np.pi) - )) - self.play(self.photon_run_along_path(line, rate_func = None)) - self.dither() - - - def get_paths(self): - squaggle = ParametricFunction( - lambda t : (0.5*t+np.cos(t))*RIGHT+np.sin(t)*UP, - start = -np.pi, - end = 2*np.pi - ) - squaggle.shift(2*UP) - start, end = squaggle.points[0], squaggle.points[-1] - line = Line(start, end) - result = [squaggle, line] - for mob in result: - mob.highlight(BLUE_D) - return result - -class PhtonBendsInWater(PhotonScene, ZoomedScene): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - kwargs = { - "density" : self.zoom_factor*DEFAULT_POINT_DENSITY_1D - } - top_line = Line(SPACE_HEIGHT*UP+2*LEFT, ORIGIN, **kwargs) - extension = Line(ORIGIN, SPACE_HEIGHT*DOWN+2*RIGHT, **kwargs) - bottom_line = Line(ORIGIN, SPACE_HEIGHT*DOWN+RIGHT, **kwargs) - path1 = Mobject(top_line, extension) - path2 = Mobject(top_line, bottom_line) - for mob in path1, path2: - mob.ingest_sub_mobjects() - extension.highlight(RED) - theta1 = np.arctan(bottom_line.get_slope()) - theta2 = np.arctan(extension.get_slope()) - arc = Arc(theta2-theta1, start_angle = theta1, radius = 2) - question_mark = TextMobject("$\\theta$?") - question_mark.shift(arc.get_center()+0.5*DOWN+0.25*RIGHT) - wave = self.wavify(path2) - wave.highlight(YELLOW) - wave.scale(0.5) - - self.add(glass) - self.play(ShowCreation(path1)) - self.play(Transform(path1, path2)) - self.dither() - # self.activate_zooming() - self.dither() - self.play(ShowPassingFlash( - wave, run_time = 3, rate_func = None - )) - self.dither() - self.play(ShowCreation(extension)) - self.play( - ShowCreation(arc), - ShimmerIn(question_mark) - ) - -class LightIsFasterInAirThanWater(ShowMultiplePathsInWater): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - equation = TexMobject("v_{\\text{air}} > v_{\\text{water}}") - equation.to_edge(UP) - path = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT) - path1 = path.copy().shift(2*UP) - path2 = path.copy().shift(2*DOWN) - - self.add(glass) - self.play(ShimmerIn(equation)) - self.dither() - photon_runs = [] - photon_runs.append(self.photon_run_along_path( - path1, rate_func = lambda t : min(1, 1.2*t) - )) - photon_runs.append(self.photon_run_along_path(path2)) - self.play(*photon_runs, **{"run_time" : 2}) - self.dither() - - -class GeometryOfGlassSituation(ShowMultiplePathsInWater): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - left = self.start_point[0]*RIGHT - right = self.end_point[0]*RIGHT - start_x = interpolate(left, right, 0.2) - end_x = interpolate(left, right, 1.0) - left_line = Line(self.start_point, left, color = RED_D) - right_line = Line(self.end_point, right, color = RED_D) - h_1, h_2 = map(TexMobject, ["h_1", "h_2"]) - h_1.next_to(left_line, LEFT) - h_2.next_to(right_line, RIGHT) - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - - x = start_x - left_brace = Brace(Mobject(Point(left), Point(x))) - right_brace = Brace(Mobject(Point(x), Point(right)), UP) - x_mob = TexMobject("x") - x_mob.next_to(left_brace, DOWN) - w_minus_x = TexMobject("w-x") - w_minus_x.next_to(right_brace, UP) - top_line = Line(self.start_point, x) - bottom_line = Line(x, self.end_point) - top_dist = TexMobject("\\sqrt{h_1^2+x^2}") - top_dist.scale(0.5) - a = 0.3 - n = top_line.get_num_points() - point = top_line.points[int(a*n)] - top_dist.next_to(Point(point), RIGHT, buff = 0.3) - bottom_dist = TexMobject("\\sqrt{h_2^2+(w-x)^2}") - bottom_dist.scale(0.5) - n = bottom_line.get_num_points() - point = bottom_line.points[int((1-a)*n)] - bottom_dist.next_to(Point(point), LEFT, buff = 1) - - end_top_line = Line(self.start_point, end_x) - end_bottom_line = Line(end_x, self.end_point) - end_brace = Brace(Mobject(Point(left), Point(end_x))) - end_x_mob = TexMobject("x").next_to(end_brace, DOWN) - - axes = Mobject( - NumberLine(), - NumberLine().rotate(np.pi/2).shift(7*LEFT) - ) - graph = FunctionGraph( - lambda x : 0.4*(x+1)*(x-3)+4, - x_min = -2, - x_max = 4 - ) - graph.highlight(YELLOW) - Mobject(axes, graph).scale(0.2).to_corner(UP+RIGHT, buff = 1) - axes.add(TexMobject("x", size = "\\small").next_to(axes, RIGHT)) - axes.add(TextMobject("Travel time", size = "\\small").next_to( - axes, UP - )) - new_graph = graph.copy() - midpoint = new_graph.points[new_graph.get_num_points()/2] - new_graph.filter_out(lambda p : p[0] < midpoint[0]) - new_graph.reverse_points() - pairs_for_end_transform = [ - (mob, mob.copy()) - for mob in top_line, bottom_line, left_brace, x_mob - ] - - self.add(glass, point_a, point_b, A, B) - line = Mobject(top_line, bottom_line).ingest_sub_mobjects() - self.play(ShowCreation(line)) - self.dither() - self.play( - GrowFromCenter(left_brace), - GrowFromCenter(x_mob) - ) - self.play( - GrowFromCenter(right_brace), - GrowFromCenter(w_minus_x) - ) - self.play(ShowCreation(left_line), ShimmerIn(h_1)) - self.play(ShowCreation(right_line), GrowFromCenter(h_2)) - self.play(ShimmerIn(top_dist)) - self.play(GrowFromCenter(bottom_dist)) - self.dither(3) - self.clear() - self.add(glass, point_a, point_b, A, B, - top_line, bottom_line, left_brace, x_mob) - self.play(ShowCreation(axes)) - kwargs = { - "run_time" : 4, - } - self.play(*[ - Transform(*pair, **kwargs) - for pair in [ - (top_line, end_top_line), - (bottom_line, end_bottom_line), - (left_brace, end_brace), - (x_mob, end_x_mob) - ] - ]+[ShowCreation(graph, **kwargs)]) - self.dither() - self.show_derivatives(graph) - line = self.show_derivatives(new_graph) - self.add(line) - self.play(*[ - Transform(*pair, rate_func = lambda x : 0.3*smooth(x)) - for pair in pairs_for_end_transform - ]) - self.dither() - - def show_derivatives(self, graph, run_time = 2): - step = self.frame_duration/run_time - for a in smooth(np.arange(0, 1-step, step)): - index = int(a*graph.get_num_points()) - p1, p2 = graph.points[index], graph.points[index+1] - line = Line(LEFT, RIGHT) - line.rotate(angle_of_vector(p2-p1)) - line.shift(p1) - self.add(line) - self.dither(self.frame_duration) - self.remove(line) - return line - - -class Spring(Line): - CONFIG = { - "num_loops" : 5, - "loop_radius" : 0.3, - "color" : GREY - } - - def generate_points(self): - ## self.start, self.end - length = np.linalg.norm(self.end-self.start) - angle = angle_of_vector(self.end-self.start) - micro_radius = self.loop_radius/length - m = 2*np.pi*(self.num_loops+0.5) - def loop(t): - return micro_radius*( - RIGHT + np.cos(m*t)*LEFT + np.sin(m*t)*UP - ) - new_epsilon = self.epsilon/(m*micro_radius)/length - - self.add_points([ - t*RIGHT + loop(t) - for t in np.arange(0, 1, new_epsilon) - ]) - self.scale(length/(1+2*micro_radius)) - self.rotate(angle) - self.shift(self.start) - - -class SpringSetup(ShowMultiplePathsInWater): - def construct(self): - self.ring_shift_val = 6*RIGHT - self.slide_kwargs = { - "rate_func" : there_and_back, - "run_time" : 5 - } - - self.setup_background() - rod = Region( - lambda x, y : (abs(x) < 5) & (abs(y) < 0.05), - color = GOLD_E - ) - ring = Arc( - angle = 11*np.pi/6, - start_angle = -11*np.pi/12, - radius = 0.2, - color = YELLOW - ) - ring.shift(-self.ring_shift_val/2) - self.generate_springs(ring) - - - self.add_rod_and_ring(rod, ring) - self.slide_ring(ring) - self.dither() - self.add_springs() - self.add_force_definitions() - self.slide_system(ring) - self.show_horizontal_component(ring) - self.show_angles(ring) - self.show_equation() - - - def setup_background(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - self.add(glass, point_a, point_b, A, B) - - def generate_springs(self, ring): - self.start_springs, self.end_springs = [ - Mobject( - Spring(self.start_point, r.get_top()), - Spring(self.end_point, r.get_bottom()) - ) - for r in ring, ring.copy().shift(self.ring_shift_val) - ] - - def add_rod_and_ring(self, rod, ring): - rod_word = TextMobject("Rod") - rod_word.next_to(Point(), UP) - ring_word = TextMobject("Ring") - ring_word.next_to(ring, UP) - self.dither() - self.add(rod) - self.play(ShimmerIn(rod_word)) - self.dither() - self.remove(rod_word) - self.play(ShowCreation(ring)) - self.play(ShimmerIn(ring_word)) - self.dither() - self.remove(ring_word) - - def slide_ring(self, ring): - self.play(ApplyMethod( - ring.shift, self.ring_shift_val, - **self.slide_kwargs - )) - - def add_springs(self): - colors = iter([BLACK, BLUE_E]) - for spring in self.start_springs.split(): - circle = Circle(color = colors.next()) - circle.reverse_points() - circle.scale(spring.loop_radius) - circle.shift(spring.points[0]) - - self.play(Transform(circle, spring)) - self.remove(circle) - self.add(spring) - self.dither() - - def add_force_definitions(self): - top_force = TexMobject("F_1 = \\dfrac{1}{v_{\\text{air}}}") - bottom_force = TexMobject("F_2 = \\dfrac{1}{v_{\\text{water}}}") - top_spring, bottom_spring = self.start_springs.split() - top_force.next_to(top_spring) - bottom_force.next_to(bottom_spring, DOWN, buff = -0.5) - words = TextMobject(""" - The force in a real spring is - proportional to that spring's length - """) - words.to_corner(UP+RIGHT) - for force in top_force, bottom_force: - self.play(GrowFromCenter(force)) - self.dither() - self.play(ShimmerIn(words)) - self.dither(3) - self.remove(top_force, bottom_force, words) - - def slide_system(self, ring): - equilibrium_slide_kwargs = dict(self.slide_kwargs) - def jiggle_to_equilibrium(t): - return 0.7*(1+((1-t)**2)*(-np.cos(10*np.pi*t))) - equilibrium_slide_kwargs = { - "rate_func" : jiggle_to_equilibrium, - "run_time" : 3 - } - start = Mobject(ring, self.start_springs) - end = Mobject( - ring.copy().shift(self.ring_shift_val), - self.end_springs - ) - for kwargs in self.slide_kwargs, equilibrium_slide_kwargs: - self.play(Transform(start, end, **kwargs)) - self.dither() - - def show_horizontal_component(self, ring): - v_right = Vector(ring.get_top(), RIGHT) - v_left = Vector(ring.get_bottom(), LEFT) - self.play(*map(ShowCreation, [v_right, v_left])) - self.dither() - self.remove(v_right, v_left) - - def show_angles(self, ring): - ring_center = ring.get_center() - lines, arcs, thetas = [], [], [] - counter = it.count(1) - for point in self.start_point, self.end_point: - line = Line(point, ring_center, color = GREY) - angle = np.pi/2-np.abs(np.arctan(line.get_slope())) - arc = Arc(angle, radius = 0.5).rotate(np.pi/2) - if point is self.end_point: - arc.rotate(np.pi) - theta = TexMobject("\\theta_%d"%counter.next()) - theta.scale(0.5) - theta.shift(2*arc.get_center()) - arc.shift(ring_center) - theta.shift(ring_center) - - lines.append(line) - arcs.append(arc) - thetas.append(theta) - vert_line = Line(2*UP, 2*DOWN) - vert_line.shift(ring_center) - top_spring, bottom_spring = self.start_springs.split() - - self.play( - Transform(ring, Point(ring_center)), - Transform(top_spring, lines[0]), - Transform(bottom_spring, lines[1]) - ) - self.play(ShowCreation(vert_line)) - anims = [] - for arc, theta in zip(arcs, thetas): - anims += [ - ShowCreation(arc), - GrowFromCenter(theta) - ] - self.play(*anims) - self.dither() - - def show_equation(self): - equation = TexMobject([ - "\\left(\\dfrac{1}{\\phantom{v_air}}\\right)", - "\\sin(\\theta_1)", - "=", - "\\left(\\dfrac{1}{\\phantom{v_water}}\\right)", - "\\sin(\\theta_2)" - ]) - equation.to_corner(UP+RIGHT) - frac1, sin1, equals, frac2, sin2 = equation.split() - v_air, v_water = [ - TexMobject("v_{\\text{%s}}"%s, size = "\\Large") - for s in "air", "water" - ] - v_air.next_to(Point(frac1.get_center()), DOWN) - v_water.next_to(Point(frac2.get_center()), DOWN) - frac1.add(v_air) - frac2.add(v_water) - f1, f2 = [ - TexMobject("F_%d"%d, size = "\\Large") - for d in 1, 2 - ] - f1.next_to(sin1, LEFT) - f2.next_to(equals, RIGHT) - sin2_start = sin2.copy().next_to(f2, RIGHT) - bar1 = TexMobject("\\dfrac{\\qquad}{\\qquad}") - bar2 = bar1.copy() - bar1.next_to(sin1, DOWN) - bar2.next_to(sin2, DOWN) - v_air_copy = v_air.copy().next_to(bar1, DOWN) - v_water_copy = v_water.copy().next_to(bar2, DOWN) - bars = Mobject(bar1, bar2) - new_eq = equals.copy().center().shift(bars.get_center()) - snells = TextMobject("Snell's Law") - snells.highlight(YELLOW) - snells.shift(new_eq.get_center()[0]*RIGHT) - snells.shift(UP) - - anims = [] - for mob in f1, sin1, equals, f2, sin2_start: - anims.append(ShimmerIn(mob)) - self.play(*anims) - self.dither() - for f, frac in (f1, frac1), (f2, frac2): - target = frac.copy().ingest_sub_mobjects() - also = [] - if f is f2: - also.append(Transform(sin2_start, sin2)) - sin2 = sin2_start - self.play(Transform(f, target), *also) - self.remove(f) - self.add(frac) - self.dither() - self.play( - FadeOut(frac1), - FadeOut(frac2), - Transform(v_air, v_air_copy), - Transform(v_water, v_water_copy), - ShowCreation(bars), - Transform(equals, new_eq) - ) - self.dither() - frac1 = Mobject(sin1, bar1, v_air) - frac2 = Mobject(sin2, bar2, v_water) - for frac, vect in (frac1, LEFT), (frac2, RIGHT): - self.play(ApplyMethod( - frac.next_to, equals, vect - )) - self.dither() - self.play(ShimmerIn(snells)) - self.dither() - -class WhatGovernsTheSpeedOfLight(PhotonScene, PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - - self.add(self.cycloid) - self.slide(randy, self.cycloid) - self.play(self.photon_run_along_path(self.cycloid)) - - self.dither() - -class WhichPathWouldLightTake(PhotonScene, TryManyPaths): - def construct(self): - words = TextMobject( - ["Which path ", "would \\emph{light} take", "?"] - ) - words.split()[1].highlight(YELLOW) - words.to_corner(UP+RIGHT) - self.add_cycloid_end_points() - - anims = [ - self.photon_run_along_path( - path, - rate_func = smooth - ) - for path in self.get_paths() - ] - self.play(anims[0], ShimmerIn(words)) - for anim in anims[1:]: - self.play(anim) - - - -class StateSnellsLaw(PhotonScene): - def construct(self): - point_a = 3*LEFT+3*UP - point_b = 1.5*RIGHT+3*DOWN - midpoint = ORIGIN - - lines, arcs, thetas = [], [], [] - counter = it.count(1) - for point in point_a, point_b: - line = Line(point, midpoint, color = RED_D) - angle = np.pi/2-np.abs(np.arctan(line.get_slope())) - arc = Arc(angle, radius = 0.5).rotate(np.pi/2) - if point is point_b: - arc.rotate(np.pi) - line.reverse_points() - theta = TexMobject("\\theta_%d"%counter.next()) - theta.scale(0.5) - theta.shift(2*arc.get_center()) - arc.shift(midpoint) - theta.shift(midpoint) - - lines.append(line) - arcs.append(arc) - thetas.append(theta) - vert_line = Line(2*UP, 2*DOWN) - vert_line.shift(midpoint) - path = Mobject(*lines).ingest_sub_mobjects() - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.add(glass) - equation = TexMobject([ - "\\dfrac{\\sin(\\theta_1)}{v_{\\text{air}}}", - "=", - "\\dfrac{\\sin(\\theta_2)}{v_{\\text{water}}}", - ]) - equation.to_corner(UP+RIGHT) - exp1, equals, exp2 = equation.split() - snells_law = TextMobject("Snell's Law:") - snells_law.highlight(YELLOW) - snells_law.to_edge(UP) - - self.play(ShimmerIn(snells_law)) - self.dither() - self.play(ShowCreation(path)) - self.play(self.photon_run_along_path(path)) - self.dither() - self.play(ShowCreation(vert_line)) - self.play(*map(ShowCreation, arcs)) - self.play(*map(GrowFromCenter, thetas)) - self.dither() - self.play(ShimmerIn(exp1)) - self.dither() - self.play(*map(ShimmerIn, [equals, exp2])) - self.dither() - - - - - - - - - - diff --git a/brachistochrone/misc.py b/brachistochrone/misc.py deleted file mode 100644 index 2f4cbf45..00000000 --- a/brachistochrone/misc.py +++ /dev/null @@ -1,512 +0,0 @@ -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 ImageMobject -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import * -from topics.characters import * -from topics.functions import ParametricFunction, FunctionGraph -from topics.number_line import * -from mobject.region import Region, region_from_polygon_vertices -from topics.three_dimensions import Stars -from scene import Scene - -from brachistochrone.curves import Cycloid - -class PhysicalIntuition(Scene): - def construct(self): - n_terms = 4 - def func((x, y, ignore)): - z = complex(x, y) - if (np.abs(x%1 - 0.5)<0.01 and y < 0.01) or np.abs(z)<0.01: - return ORIGIN - out_z = 1./(2*np.tan(np.pi*z)*(z**2)) - return out_z.real*RIGHT - out_z.imag*UP - arrows = Mobject(*[ - Arrow(ORIGIN, np.sqrt(2)*point) - for point in compass_directions(4, RIGHT+UP) - ]) - arrows.highlight(YELLOW) - arrows.ingest_sub_mobjects() - all_arrows = Mobject(*[ - arrows.copy().scale(0.3/(x)).shift(x*RIGHT) - for x in range(1, n_terms+2) - ]) - terms = TexMobject([ - "\\dfrac{1}{%d^2} + "%(x+1) - for x in range(n_terms) - ]+["\\cdots"]) - terms.shift(2*UP) - plane = NumberPlane(color = BLUE_E) - axes = Mobject(NumberLine(), NumberLine().rotate(np.pi/2)) - axes.highlight(WHITE) - - for term in terms.split(): - self.play(ShimmerIn(term, run_time = 0.5)) - self.dither() - self.play(ShowCreation(plane), ShowCreation(axes)) - self.play(*[ - Transform(*pair) - for pair in zip(terms.split(), all_arrows.split()) - ]) - self.play(PhaseFlow( - func, plane, - run_time = 5, - virtual_time = 8 - )) - - - -class TimeLine(Scene): - def construct(self): - dated_events = [ - { - "date" : 1696, - "text": "Johann Bernoulli poses Brachistochrone problem", - "picture" : "Johann_Bernoulli2" - }, - { - "date" : 1662, - "text" : "Fermat states his principle of least time", - "picture" : "Pierre_de_Fermat" - } - ] - speical_dates = [2016] + [ - obj["date"] for obj in dated_events - ] - centuries = range(1600, 2100, 100) - timeline = NumberLine( - numerical_radius = 300, - number_at_center = 1800, - unit_length_to_spatial_width = SPACE_WIDTH/100, - tick_frequency = 10, - numbers_with_elongated_ticks = centuries - ) - timeline.add_numbers(*centuries) - centers = [ - Point(timeline.number_to_point(year)) - for year in speical_dates - ] - timeline.add(*centers) - timeline.shift(-centers[0].get_center()) - - self.add(timeline) - self.dither() - run_times = iter([3, 1]) - for point, event in zip(centers[1:], dated_events): - self.play(ApplyMethod( - timeline.shift, -point.get_center(), - run_time = run_times.next() - )) - picture = ImageMobject(event["picture"], invert = False) - picture.scale_to_fit_width(2) - picture.to_corner(UP+RIGHT) - event_mob = TextMobject(event["text"]) - event_mob.shift(2*LEFT+2*UP) - date_mob = TexMobject(str(event["date"])) - date_mob.scale(0.5) - date_mob.shift(0.6*UP) - line = Line(event_mob.get_bottom(), 0.2*UP) - self.play( - ShimmerIn(event_mob), - ShowCreation(line), - ShimmerIn(date_mob) - ) - self.play(FadeIn(picture)) - self.dither(3) - self.play(*map(FadeOut, [event_mob, date_mob, line, picture])) - - -class StayedUpAllNight(Scene): - def construct(self): - clock = Circle(radius = 2, color = WHITE) - clock.add(Dot(ORIGIN)) - ticks = Mobject(*[ - Line(1.8*vect, 2*vect, color = GREY) - for vect in compass_directions(12) - ]) - clock.add(ticks) - hour_hand = Line(ORIGIN, UP) - minute_hand = Line(ORIGIN, 1.5*UP) - clock.add(hour_hand, minute_hand) - clock.to_corner(UP+RIGHT) - hour_hand.get_center = lambda : clock.get_center() - minute_hand.get_center = lambda : clock.get_center() - - solution = ImageMobject( - "Newton_brachistochrone_solution2", - use_cache = False - ) - solution.point_thickness = 3 - solution.highlight(GREY) - solution.scale_to_fit_width(5) - solution.to_corner(UP+RIGHT) - newton = ImageMobject("Old_Newton", invert = False) - newton.scale(0.8) - phil_trans = TextMobject("Philosophical Transactions") - rect = Rectangle(height = 6, width = 4.5, color = WHITE) - rect.to_corner(UP+RIGHT) - rect.shift(DOWN) - phil_trans.scale_to_fit_width(0.8*rect.get_width()) - phil_trans.next_to(Point(rect.get_top()), DOWN) - new_solution = solution.copy() - new_solution.scale_to_fit_width(phil_trans.get_width()) - new_solution.next_to(phil_trans, DOWN, buff = 1) - not_newton = TextMobject("-Totally not by Newton") - not_newton.scale_to_fit_width(2.5) - not_newton.next_to(new_solution, DOWN, aligned_edge = RIGHT) - phil_trans.add(rect) - - newton_complaint = TextMobject([ - "``I do not love to be", - " \\emph{dunned} ", - "and teased by foreigners''" - ], size = "\\small") - newton_complaint.to_edge(UP, buff = 0.2) - dunned = newton_complaint.split()[1] - dunned.highlight() - dunned_def = TextMobject("(old timey term for making \\\\ demands on someone)") - dunned_def.scale(0.7) - dunned_def.next_to(phil_trans, LEFT) - dunned_def.shift(2*UP) - dunned_arrow = Arrow(dunned_def, dunned) - - johann = ImageMobject("Johann_Bernoulli2", invert = False) - johann.scale(0.4) - johann.to_edge(LEFT) - johann.shift(DOWN) - johann_quote = TextMobject("``I recognize the lion by his claw''") - johann_quote.next_to(johann, UP, aligned_edge = LEFT) - - self.play(ApplyMethod(newton.to_edge, LEFT)) - self.play(ShowCreation(clock)) - kwargs = { - "axis" : OUT, - "rate_func" : smooth - } - self.play( - Rotating(hour_hand, radians = -2*np.pi, **kwargs), - Rotating(minute_hand, radians = -12*2*np.pi, **kwargs), - run_time = 5 - ) - self.dither() - self.clear() - self.add(newton) - clock.ingest_sub_mobjects() - self.play(Transform(clock, solution)) - self.remove(clock) - self.add(solution) - self.dither() - self.play( - FadeIn(phil_trans), - Transform(solution, new_solution) - ) - self.dither() - self.play(ShimmerIn(not_newton)) - phil_trans.add(solution, not_newton) - self.dither() - self.play(*map(ShimmerIn, newton_complaint.split())) - self.dither() - self.play( - ShimmerIn(dunned_def), - ShowCreation(dunned_arrow) - ) - self.dither() - self.remove(dunned_def, dunned_arrow) - self.play(FadeOut(newton_complaint)) - self.remove(newton_complaint) - self.play( - FadeOut(newton), - GrowFromCenter(johann) - ) - self.remove(newton) - self.dither() - self.play(ShimmerIn(johann_quote)) - self.dither() - - -class ThetaTGraph(Scene): - def construct(self): - t_axis = NumberLine() - theta_axis = NumberLine().rotate(np.pi/2) - theta_mob = TexMobject("\\theta(t)") - t_mob = TexMobject("t") - theta_mob.next_to(theta_axis, RIGHT) - theta_mob.to_edge(UP) - t_mob.next_to(t_axis, UP) - t_mob.to_edge(RIGHT) - graph = ParametricFunction( - lambda t : 4*t*RIGHT + 2*smooth(t)*UP - ) - line = Line(graph.points[0], graph.points[-1], color = WHITE) - q_mark = TextMobject("?") - q_mark.next_to(Point(graph.get_center()), LEFT) - stars = Stars(color = BLACK) - stars.scale(0.1).shift(q_mark.get_center()) - - - squiggle = ParametricFunction( - lambda t : t*RIGHT + 0.2*t*(5-t)*(np.sin(t)**2)*UP, - start = 0, - end = 5 - ) - - self.play( - ShowCreation(t_axis), - ShowCreation(theta_axis), - ShimmerIn(theta_mob), - ShimmerIn(t_mob) - ) - self.play( - ShimmerIn(q_mark), - ShowCreation(graph) - ) - self.dither() - self.play( - Transform(q_mark, stars), - Transform(graph, line) - ) - self.dither() - self.play(Transform(graph, squiggle)) - self.dither() - - -class SolutionsToTheBrachistochrone(Scene): - def construct(self): - r_range = np.arange(0.5, 2, 0.25) - cycloids = Mobject(*[ - Cycloid(radius = r, end_theta=2*np.pi) - for r in r_range - ]) - lower_left = 2*DOWN+6*LEFT - lines = Mobject(*[ - Line( - lower_left, - lower_left+5*r*np.cos(np.arctan(r))*RIGHT+2*r*np.sin(np.arctan(r))*UP - ) - for r in r_range - ]) - nl = NumberLine(numbers_with_elongated_ticks = []) - x_axis = nl.copy().shift(3*UP) - y_axis = nl.copy().rotate(np.pi/2).shift(6*LEFT) - t_axis = nl.copy().shift(2*DOWN) - x_label = TexMobject("x") - x_label.next_to(x_axis, DOWN) - x_label.to_edge(RIGHT) - y_label = TexMobject("y") - y_label.next_to(y_axis, RIGHT) - y_label.shift(2*DOWN) - t_label = TexMobject("t") - t_label.next_to(t_axis, UP) - t_label.to_edge(RIGHT) - theta_label = TexMobject("\\theta") - theta_label.next_to(y_axis, RIGHT) - theta_label.to_edge(UP) - words = TextMobject("Boundary conditions?") - words.next_to(lines, RIGHT) - words.shift(2*UP) - - self.play(ShowCreation(x_axis), ShimmerIn(x_label)) - self.play(ShowCreation(y_axis), ShimmerIn(y_label)) - self.play(ShowCreation(cycloids)) - self.dither() - self.play( - Transform(cycloids, lines), - Transform(x_axis, t_axis), - Transform(x_label, t_label), - Transform(y_label, theta_label), - run_time = 2 - ) - self.dither() - self.play(ShimmerIn(words)) - self.dither() - - -class VideoLayout(Scene): - def construct(self): - left, right = 5*LEFT, 5*RIGHT - top_words = TextMobject("The next 15 minutes of your life:") - top_words.to_edge(UP) - line = Line(left, right, color = BLUE_D) - for a in np.arange(0, 4./3, 1./3): - vect = interpolate(left, right, a) - line.add_line(vect+0.2*DOWN, vect+0.2*UP) - left_brace = Brace( - Mobject( - Point(left), - Point(interpolate(left, right, 2./3)) - ), - DOWN - ) - right_brace = Brace( - Mobject( - Point(interpolate(left, right, 2./3)), - Point(right) - ), - UP - ) - left_brace.words = map(TextMobject, [ - "Problem statement", - "History", - "Johann Bernoulli's cleverness" - ]) - curr = left_brace - right_brace.words = map(TextMobject, [ - "Challenge", - "Mark Levi's cleverness", - ]) - for brace in left_brace, right_brace: - curr = brace - direction = DOWN if brace is left_brace else UP - for word in brace.words: - word.next_to(curr, direction) - curr = word - right_brace.words.reverse() - - self.play(ShimmerIn(top_words)) - self.play(ShowCreation(line)) - for brace in left_brace, right_brace: - self.play(GrowFromCenter(brace)) - self.dither() - for word in brace.words: - self.play(ShimmerIn(word)) - self.dither() - - - - -class ShortestPathProblem(Scene): - def construct(self): - point_a, point_b = 3*LEFT, 3*RIGHT - dots = [] - for point, char in [(point_a, "A"), (point_b, "B")]: - dot = Dot(point) - letter = TexMobject(char) - letter.next_to(dot, UP+LEFT) - dot.add(letter) - dots.append(dot) - - path = ParametricFunction( - lambda t : (t/2 + np.cos(t))*RIGHT + np.sin(t)*UP, - start = -2*np.pi, - end = 2*np.pi - ) - path.scale(6/(2*np.pi)) - path.shift(point_a - path.points[0]) - path.highlight(RED) - line = Line(point_a, point_b) - words = TextMobject("Shortest path from $A$ to $B$") - words.to_edge(UP) - - self.play( - ShimmerIn(words), - *map(GrowFromCenter, dots) - ) - self.play(ShowCreation(path)) - self.play(Transform( - path, line, - path_func = path_along_arc(np.pi) - )) - self.dither() - - -class MathBetterThanTalking(Scene): - def construct(self): - mathy = Mathematician() - mathy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble() - bubble.pin_to(mathy) - bubble.write("Math $>$ Talking about math") - - self.add(mathy) - self.play(ShowCreation(bubble)) - self.play(ShimmerIn(bubble.content)) - self.dither() - self.play(ApplyMethod( - mathy.blink, - rate_func = squish_rate_func(there_and_back, 0.4, 0.6) - )) - - -class DetailsOfProofBox(Scene): - def construct(self): - rect = Rectangle(height = 4, width = 6, color = WHITE) - words = TextMobject("Details of proof") - words.to_edge(UP) - - self.play( - ShowCreation(rect), - ShimmerIn(words) - ) - self.dither() - - - -class TalkedAboutSnellsLaw(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - morty = Mortimer() - morty.to_edge(DOWN+RIGHT) - randy.bubble = SpeechBubble().pin_to(randy) - morty.bubble = SpeechBubble().pin_to(morty) - - phrases = [ - "Let's talk about Snell's law", - "I love Snell's law", - "It's like running from \\\\ a beach into the ocean", - "It's like two constant \\\\ tension springs", - ] - - self.add(randy, morty) - talkers = it.cycle([randy, morty]) - for talker, phrase in zip(talkers, phrases): - talker.bubble.write(phrase) - self.play( - FadeIn(talker.bubble), - ShimmerIn(talker.bubble.content) - ) - self.play(ApplyMethod( - talker.blink, - rate_func = squish_rate_func(there_and_back) - )) - self.dither() - self.remove(talker.bubble, talker.bubble.content) - - -class YetAnotherMarkLevi(Scene): - def construct(self): - words = TextMobject("Yet another bit of Mark Levi cleverness") - words.to_edge(UP) - levi = ImageMobject("Mark_Levi", invert = False) - levi.scale_to_fit_width(6) - levi.show() - - self.add(levi) - self.play(ShimmerIn(words)) - self.dither(2) - - - - - - - - - - - - - - - - diff --git a/brachistochrone/multilayered.py b/brachistochrone/multilayered.py deleted file mode 100644 index a1d7ff72..00000000 --- a/brachistochrone/multilayered.py +++ /dev/null @@ -1,498 +0,0 @@ -import numpy as np -import itertools as it - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject, Mobject1D -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from topics.geometry import * -from topics.characters import Randolph -from topics.functions import * -from mobject.region import Region -from scene import Scene -from scene.zoomed_scene import ZoomedScene - -from camera import Camera -from brachistochrone.light import PhotonScene -from brachistochrone.curves import * - - -class MultilayeredScene(Scene): - CONFIG = { - "n_layers" : 5, - "top_color" : BLUE_E, - "bottom_color" : BLUE_A, - "total_glass_height" : 5, - "top" : 3*UP, - "RectClass" : Rectangle #FilledRectangle - } - - def get_layers(self, n_layers = None): - if n_layers is None: - n_layers = self.n_layers - width = 2*SPACE_WIDTH - height = float(self.total_glass_height)/n_layers - rgb_pair = [ - np.array(Color(color).get_rgb()) - for color in self.top_color, self.bottom_color - ] - rgb_range = [ - interpolate(*rgb_pair+[x]) - for x in np.arange(0, 1, 1./n_layers) - ] - tops = [ - self.top + x*height*DOWN - for x in range(n_layers) - ] - color = Color() - result = [] - for top, rgb in zip(tops, rgb_range): - color.set_rgb(rgb) - rect = self.RectClass( - height = height, - width = width, - color = color - ) - rect.shift(top-rect.get_top()) - result.append(rect) - return result - - def add_layers(self): - self.layers = self.get_layers() - self.add(*self.layers) - self.freeze_background() - - def get_bottom(self): - return self.top + self.total_glass_height*DOWN - - def get_continuous_glass(self): - result = self.RectClass( - width = 2*SPACE_WIDTH, - height = self.total_glass_height, - ) - result.sort_points(lambda p : -p[1]) - result.gradient_highlight(self.top_color, self.bottom_color) - result.shift(self.top-result.get_top()) - return result - - -class TwoToMany(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_glass() - layers = self.get_layers() - - self.add(glass) - self.dither() - self.play(*[ - FadeIn( - layer, - rate_func = squish_rate_func(smooth, x, 1) - ) - for layer, x in zip(layers[1:], it.count(0, 0.2)) - ]+[ - Transform(glass, layers[0]) - ]) - self.dither() - - def get_glass(self): - return self.RectClass( - height = SPACE_HEIGHT, - width = 2*SPACE_WIDTH, - color = BLUE_E - ).shift(SPACE_HEIGHT*DOWN/2) - - -class RaceLightInLayers(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - line = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT) - lines = [ - line.copy().shift(layer.get_center()) - for layer in self.layers - ] - - def rate_maker(x): - return lambda t : min(x*x*t, 1) - min_rate, max_rate = 1., 2. - rates = np.arange(min_rate, max_rate, (max_rate-min_rate)/self.n_layers) - self.play(*[ - self.photon_run_along_path( - line, - rate_func = rate_maker(rate), - run_time = 2 - ) - for line, rate in zip(lines, rates) - ]) - -class ShowDiscretePath(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - self.cycloid = Cycloid(end_theta = np.pi) - - self.generate_discrete_path() - self.play(ShowCreation(self.discrete_path)) - self.dither() - self.play(self.photon_run_along_path( - self.discrete_path, - rate_func = rush_into, - run_time = 3 - )) - self.dither() - - - def generate_discrete_path(self): - points = self.cycloid.points - tops = [mob.get_top()[1] for mob in self.layers] - tops.append(tops[-1]-self.layers[0].get_height()) - indices = [ - np.argmin(np.abs(points[:, 1]-top)) - for top in tops - ] - self.bend_points = points[indices[1:-1]] - self.path_angles = [] - self.discrete_path = Mobject1D( - color = WHITE, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - for start, end in zip(indices, indices[1:]): - start_point, end_point = points[start], points[end] - self.discrete_path.add_line( - start_point, end_point - ) - self.path_angles.append( - angle_of_vector(start_point-end_point)-np.pi/2 - ) - self.discrete_path.add_line( - points[end], SPACE_WIDTH*RIGHT+(tops[-1]-0.5)*UP - ) - -class NLayers(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - brace = Brace( - Mobject( - Point(self.top), - Point(self.get_bottom()) - ), - RIGHT - ) - n_layers = TextMobject("$n$ layers") - n_layers.next_to(brace) - - self.dither() - - self.add(brace) - self.show_frame() - - self.play( - GrowFromCenter(brace), - GrowFromCenter(n_layers) - ) - self.dither() - -class ShowLayerVariables(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - v_equations = [] - start_ys = [] - end_ys = [] - center_paths = [] - braces = [] - for layer, x in zip(self.layers[:3], it.count(1)): - eq_mob = TexMobject( - ["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"], - size = "\\Large" - ) - eq_mob.shift(layer.get_center()+2*LEFT) - v_eq = eq_mob.split() - v_eq[0].highlight(layer.get_color()) - path = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT) - path.shift(layer.get_center()) - brace_endpoints = Mobject( - Point(self.top), - Point(layer.get_bottom()) - ) - brace = Brace(brace_endpoints, RIGHT) - brace.shift(x*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()) - nudge = 0.2*RIGHT - end_y.shift(nudge) - - v_equations.append(v_eq) - start_ys.append(start_y) - end_ys.append(end_y) - center_paths.append(path) - 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( - FadeToColor(v_eq[0], WHITE), - photon_run, - run_time = time - ) - self.dither() - - starts = [0, 0.3, 0.6] - self.play(*it.chain(*[ - [ - GrowFromCenter( - mob, - rate_func=squish_rate_func(smooth, start, 1) - ) - for mob, start in zip(mobs, starts) - ] - for mobs in start_ys, braces - ])) - self.dither() - - triplets = zip(v_equations, start_ys, end_ys) - anims = [] - for v_eq, start_y, end_y in triplets: - anims += [ - ShowCreation(v_eq[1]), - ShowCreation(v_eq[2]), - Transform(start_y.copy(), end_y) - ] - self.play(*anims) - self.dither() - - -class LimitingProcess(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - num_iterations = 3 - layer_sets = [ - self.get_layers((2**x)*self.n_layers) - for x in range(num_iterations) - ] - glass_sets = [ - Mobject(*[ - Mobject( - *layer_sets[x][(2**x)*index:(2**x)*(index+1)] - ) - for index in range(self.n_layers) - ]).ingest_sub_mobjects() - for x in range(num_iterations) - ] - glass_sets.append(self.get_continuous_glass()) - for glass_set in glass_sets: - glass_set.sort_points(lambda p : p[1]) - curr_set = glass_sets[0] - self.add(curr_set) - for layer_set in glass_sets[1:]: - self.dither() - self.play(Transform(curr_set, layer_set)) - self.dither() - - - -class ShowLightAndSlidingObject(MultilayeredScene, TryManyPaths, PhotonScene): - CONFIG = { - "show_time" : False, - "dither_and_add" : False, - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_continuous_glass() - self.play(ApplyMethod(glass.fade, 0.8)) - self.freeze_background() - - paths = self.get_paths() - for path in paths: - if path.get_height() > self.total_glass_height: - path.stretch(0.7, 1) - path.shift(self.top - path.get_top()) - path.rgbs[:,2] = 0 - loop = paths.pop(1) ##Bad! - randy = Randolph() - randy.scale(RANDY_SCALE_VAL) - randy.shift(-randy.get_bottom()) - photon_run = self.photon_run_along_path( - loop, - rate_func = lambda t : smooth(1.2*t, 2), - run_time = 4.1 - ) - text = self.get_text().to_edge(UP, buff = 0.2) - - self.play(ShowCreation(loop)) - self.dither() - self.play(photon_run) - self.remove(photon_run.mobject) - randy = self.slide(randy, loop) - self.add(randy) - self.dither() - self.remove(randy) - self.play(ShimmerIn(text)) - for path in paths: - self.play(Transform( - loop, path, - path_func = path_along_arc(np.pi/2), - run_time = 2 - )) - - -class ContinuouslyObeyingSnellsLaw(MultilayeredScene): - CONFIG = { - "arc_radius" : 0.5, - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_continuous_glass() - self.add(glass) - self.freeze_background() - - cycloid = Cycloid(end_theta = np.pi) - cycloid.highlight(YELLOW) - chopped_cycloid = cycloid.copy() - n = cycloid.get_num_points() - chopped_cycloid.filter_out(lambda p : p[1] > 1 and p[0] < 0) - chopped_cycloid.reverse_points() - - - self.play(ShowCreation(cycloid)) - ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid) - self.show_equation(chopped_cycloid, ref_mob) - - def snells_law_at_every_point(self, cycloid, chopped_cycloid): - square = Square(side_length = 0.2, color = WHITE) - words = TextMobject(["Snell's law ", "everywhere"]) - snells, rest = words.split() - colon = TextMobject(":") - words.next_to(square) - words.shift(0.3*UP) - combo = Mobject(square, words) - combo.get_center = lambda : square.get_center() - new_snells = snells.copy().center().to_edge(UP, buff = 1.5) - colon.next_to(new_snells) - colon.shift(0.05*DOWN) - - self.play(MoveAlongPath( - combo, cycloid, - run_time = 5 - )) - self.play(MoveAlongPath( - combo, chopped_cycloid, - run_time = 4 - )) - dot = Dot(combo.get_center()) - self.play(Transform(square, dot)) - self.play( - Transform(snells, new_snells), - Transform(rest, colon) - ) - self.dither() - return colon - - def get_marks(self, point1, point2): - vert_line = Line(2*DOWN, 2*UP) - tangent_line = vert_line.copy() - theta = TexMobject("\\theta") - theta.scale(0.5) - angle = angle_of_vector(point1 - point2) - tangent_line.rotate( - angle - tangent_line.get_angle() - ) - angle_from_vert = angle - np.pi/2 - for mob in vert_line, tangent_line: - mob.shift(point1 - mob.get_center()) - arc = Arc(angle_from_vert, start_angle = np.pi/2) - arc.scale(self.arc_radius) - arc.shift(point1) - vect_angle = angle_from_vert/2 + np.pi/2 - vect = rotate_vector(RIGHT, vect_angle) - theta.center() - theta.shift(point1) - theta.shift(1.5*self.arc_radius*vect) - return arc, theta, vert_line, tangent_line - - - def show_equation(self, chopped_cycloid, ref_mob): - point2, point1 = chopped_cycloid.points[-2:] - arc, theta, vert_line, tangent_line = self.get_marks( - point1, point2 - ) - equation = TexMobject([ - "\\sin(\\theta)", - "\\over \\sqrt{y}", - ]) - sin, sqrt_y = equation.split() - equation.next_to(ref_mob) - const = TexMobject(" = \\text{constant}") - const.next_to(equation) - ceil_point = np.array(point1) - ceil_point[1] = self.top[1] - brace = Brace( - Mobject(Point(point1), Point(ceil_point)), - RIGHT - ) - y_mob = TexMobject("y").next_to(brace) - - self.play( - GrowFromCenter(sin), - ShowCreation(arc), - GrowFromCenter(theta) - ) - self.play(ShowCreation(vert_line)) - self.play(ShowCreation(tangent_line)) - self.dither() - self.play( - GrowFromCenter(sqrt_y), - GrowFromCenter(brace), - GrowFromCenter(y_mob) - ) - self.dither() - self.play(Transform( - Point(const.get_left()), const - )) - self.dither() - - - - - - - - - - - - - - - - - - - diff --git a/brachistochrone/wordplay.py b/brachistochrone/wordplay.py deleted file mode 100644 index d61d31c0..00000000 --- a/brachistochrone/wordplay.py +++ /dev/null @@ -1,572 +0,0 @@ -import numpy as np -import itertools as it -import os - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject -from mobject.image_mobject import \ - ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import * -from animation.simple_animations import * -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import * -from topics.characters import Randolph, Mathematician -from topics.functions import ParametricFunction, FunctionGraph -from topics.number_line import NumberPlane -from mobject.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) - - self.add(name) - self.play(FadeIn(cornell)) - self.play(ShimmerIn(books_word)) - for book in books: - book.shift(5*LEFT) - self.play(ApplyMethod(book.shift, 5*RIGHT)) - self.play(ShimmerIn(contributions)) - for logo in logos: - self.play(FadeIn(logo)) - self.dither() - -class ShowTweets(Scene): - def construct(self): - tweets = [ - ImageMobject("tweet%d"%x, invert = False) - for x in range(1, 4) - ] - for tweet in tweets: - tweet.scale(0.4) - tweets[0].to_corner(UP+LEFT) - tweets[1].next_to(tweets[0], RIGHT, aligned_edge = UP) - tweets[2].next_to(tweets[1], DOWN) - - self.play(GrowFromCenter(tweets[0])) - for x in 1, 2: - self.play( - Transform(Point(tweets[x-1].get_center()), tweets[x]), - Animation(tweets[x-1]) - ) - self.dither() - -class LetsBeHonest(Scene): - def construct(self): - self.play(ShimmerIn(TextMobject(""" - Let's be honest about who benefits - from this collaboration... - """))) - self.dither() - - -class WhatIsTheBrachistochrone(Scene): - def construct(self): - self.play(ShimmerIn(TextMobject(""" - So \\dots what is the Brachistochrone? - """))) - self.dither() - - -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'' \\\\ - 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.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) - 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)) - for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]: - self.play( - ShowCreation(ar), - ShimmerIn(ex) - ) - self.dither() - -class OneSolutionTwoInsights(Scene): - def construct(self): - one_solution = TextMobject(["One ", "solution"]) - two_insights = TextMobject(["Two ", " insights"]) - two, insights = two_insights.split() - johann = ImageMobject("Johann_Bernoulli2", invert = False) - mark = ImageMobject("Mark_Levi", invert = False) - for mob in johann, mark: - mob.scale(0.4) - johann.next_to(insights, LEFT) - mark.next_to(johann, RIGHT) - name = TextMobject("Mark Levi").to_edge(UP) - - self.play(*map(ShimmerIn, one_solution.split())) - self.dither() - for pair in zip(one_solution.split(), two_insights.split()): - self.play(Transform(*pair, path_func = path_along_arc(np.pi))) - self.dither() - self.clear() - self.add(two, insights) - for word, man in [(two, johann), (insights, mark)]: - self.play( - Transform(word, Point(word.get_left())), - GrowFromCenter(man) - ) - self.dither() - self.clear() - self.play(ApplyMethod(mark.center)) - self.play(ShimmerIn(name)) - self.dither() - -class CircleOfIdeas(Scene): - def construct(self): - words = map(TextMobject, [ - "optics", "calculus", "mechanics", "geometry", "history" - ]) - words[0].highlight(YELLOW) - words[1].highlight(BLUE_D) - words[2].highlight(GREY) - words[3].highlight(GREEN) - words[4].highlight(MAROON) - brachistochrone = TextMobject("Brachistochrone") - displayed_words = [] - for word in words: - anims = self.get_spinning_anims(displayed_words) - word.shift(3*RIGHT) - point = Point() - anims.append(Transform(point, word)) - self.play(*anims) - self.remove(point) - self.add(word) - displayed_words.append(word) - self.play(*self.get_spinning_anims(displayed_words)) - self.play(*[ - Transform( - word, word.copy().highlight(BLACK).center().scale(0.1), - path_func = path_along_arc(np.pi), - rate_func = None, - run_time = 2 - ) - for word in displayed_words - ]+[ - GrowFromCenter(brachistochrone) - ]) - self.dither() - - def get_spinning_anims(self, words, angle = np.pi/6): - anims = [] - for word in words: - old_center = word.get_center() - new_center = rotate_vector(old_center, angle) - vect = new_center-old_center - anims.append(ApplyMethod( - word.shift, vect, - path_func = path_along_arc(angle), - rate_func = None - )) - return anims - - -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 - ) - 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): - spacing = 2*UP - brachy, optics, light_in_two, snells, multi = words = [ - TextMobject(text) - for text in [ - "Brachistochrone", - "Optics", - "Light in two media", - "Snell's Law", - "Multilayered glass", - ] - ] - for mob in light_in_two, snells: - mob.shift(-spacing) - arrow1 = Arrow(brachy, optics) - arrow2 = Arrow(optics, snells) - point = Point(DOWN) - - self.play(ShimmerIn(brachy)) - self.dither() - self.play( - ApplyMethod(brachy.shift, spacing), - Transform(point, optics) - ) - optics = point - arrow1 = Arrow(optics, brachy) - self.play(ShowCreation(arrow1)) - self.dither() - arrow2 = Arrow(light_in_two, optics) - self.play( - ShowCreation(arrow2), - ShimmerIn(light_in_two) - ) - self.dither() - self.play( - FadeOut(light_in_two), - GrowFromCenter(snells), - DelayByOrder( - ApplyMethod(arrow2.highlight, BLUE_D) - ) - ) - self.dither() - self.play( - FadeOut(optics), - GrowFromCenter(multi), - DelayByOrder( - ApplyMethod(arrow1.highlight, BLUE_D) - ) - ) - self.dither() - - - - - -class BalanceCompetingFactors(Scene): - args_list = [ - ("Short", "Steep"), - ("Minimal time \\\\ in water", "Short path") - ] - - @staticmethod - def args_to_string(*words): - return "".join([word.split(" ")[0] for word in words]) - - def construct(self, *words): - factor1, factor2 = [ - TextMobject("Factor %d"%x).highlight(c) - for x, c in [ - (1, RED_D), - (2, BLUE_D) - ] - ] - real_factor1, real_factor2 = map(TextMobject, words) - for word in factor1, factor2, real_factor1, real_factor2: - word.shift(0.2*UP-word.get_bottom()) - for f1 in factor1, real_factor1: - f1.highlight(RED_D) - f1.shift(2*LEFT) - for f2 in factor2, real_factor2: - f2.highlight(BLUE_D) - f2.shift(2*RIGHT) - line = Line( - factor1.get_left(), - factor2.get_right() - ) - line.center() - self.balancers = Mobject(factor1, factor2, line) - self.hidden_balancers = Mobject(real_factor1, real_factor2) - - triangle = Polygon(RIGHT, np.sqrt(3)*UP, LEFT) - triangle.next_to(line, DOWN, buff = 0) - - self.add(triangle, self.balancers) - self.rotate(1) - self.rotate(-2) - self.dither() - self.play(Transform( - factor1, real_factor1, - path_func = path_along_arc(np.pi/4) - )) - self.rotate(2) - self.dither() - self.play(Transform( - factor2, real_factor2, - path_func = path_along_arc(np.pi/4) - )) - self.rotate(-2) - self.dither() - self.rotate(1) - - def rotate(self, factor): - angle = np.pi/11 - self.play(Rotate( - self.balancers, - factor*angle, - run_time = abs(factor) - )) - self.hidden_balancers.rotate(factor*angle) - - - - -class Challenge(Scene): - def construct(self): - self.add(TextMobject(""" - Can you find a new solution to the - Brachistochrone problem by finding - an intuitive reason that time-minimizing - curves look like straight lines in - $t$-$\\theta$ space? - """)) - self.dither() - - - -class Section1(Scene): - def construct(self): - self.add(TextMobject("Section 1: Johann Bernoulli's insight")) - self.dither() - -class Section2(Scene): - def construct(self): - self.add(TextMobject( - "Section 2: Mark Levi's insight, and a challenge", - size = "\\large" - )) - self.dither() - - - -class NarratorInterjection(Scene): - def construct(self): - words1 = TexMobject("<\\text{Narrator interjection}>") - words2 = TexMobject("<\\!/\\text{Narrator interjection}>") - self.add(words1) - self.dither() - self.clear() - self.add(words2) - self.dither() - - -class ThisCouldBeTheEnd(Scene): - def construct(self): - words = TextMobject([ - "This could be the end\\dots", - "but\\dots" - ]) - for part in words.split(): - self.play(ShimmerIn(part)) - self.dither() - - -class MyOwnChallenge(Scene): - def construct(self): - self.add(TextMobject("My own challenge:")) - self.dither() - - -class WarmupChallenge(Scene): - def construct(self): - self.add(TextMobject("\\large Warm-up challenge: Confirm this for yourself")) - self.dither() - -class FindAnotherSolution(Scene): - def construct(self): - self.add(TextMobject("Find another brachistochrone solution\\dots")) - self.dither() - - -class ProofOfSnellsLaw(Scene): - def construct(self): - self.add(TextMobject("Proof of Snell's law:")) - self.dither() - - -class CondensedVersion(Scene): - def construct(self): - snells = TextMobject("Snell's") - snells.shift(-snells.get_left()) - snells.to_edge(UP) - for vect in [RIGHT, RIGHT, LEFT, DOWN, DOWN, DOWN]: - snells.add(snells.copy().next_to(snells, vect)) - snells.ingest_sub_mobjects() - snells.show() - condensed = TextMobject("condensed") - - self.add(snells) - self.dither() - self.play(DelayByOrder( - Transform(snells, condensed, run_time = 2) - )) - self.dither() - - - - - - - - - - - diff --git a/camera.py b/camera.py index 39a2d7d9..3770a249 100644 --- a/camera.py +++ b/camera.py @@ -6,8 +6,10 @@ from PIL import Image import cv2 from colour import Color import progressbar +import aggdraw from helpers import * +from mobject import PointCloudMobject, VectorizedMobject class Camera(object): CONFIG = { @@ -66,33 +68,79 @@ class Camera(object): def capture_mobjects(self, mobjects, include_sub_mobjects = True): if include_sub_mobjects: mobjects = it.chain(*[ - mob.submobject_family() + mob.nonempty_family_members() for mob in mobjects ]) + vect_mobjects = [] for mobject in mobjects: - if mobject.display_mode == "region": - self.display_region(mobject) - elif mobject.display_mode == "points": - self.display_points( + if isinstance(mobject, VectorizedMobject): + vect_mobjects.append(mobject) + elif isinstance(mobject, PointCloudMobject): + self.display_point_cloud( mobject.points, mobject.rgbs, self.adjusted_thickness(mobject.point_thickness) ) else: - raise Exception("Invalid display mode") + raise Exception("I don't know how to display that") + if vect_mobjects: + self.display_vectorized(vect_mobjects) - def display_region(self, region): - (h, w) = self.pixel_shape - scalar = 2*self.space_shape[0] / h - xs = scalar*np.arange(-w/2, w/2)+self.space_center[0] - ys = -scalar*np.arange(-h/2, h/2)+self.space_center[1] - x_array = np.dot(np.ones((h, 1)), xs.reshape((1, w))) - y_array = np.dot(ys.reshape(h, 1), np.ones((1, w))) - covered = region.condition(x_array, y_array) - rgb = np.array(Color(region.color).get_rgb()) - rgb = (255*rgb).astype('uint8') - self.pixel_array[covered] = rgb + # def display_region(self, region): + # (h, w) = self.pixel_shape + # scalar = 2*self.space_shape[0] / h + # xs = scalar*np.arange(-w/2, w/2)+self.space_center[0] + # ys = -scalar*np.arange(-h/2, h/2)+self.space_center[1] + # x_array = np.dot(np.ones((h, 1)), xs.reshape((1, w))) + # y_array = np.dot(ys.reshape(h, 1), np.ones((1, w))) + # covered = region.condition(x_array, y_array) + # rgb = np.array(Color(region.color).get_rgb()) + # rgb = (255*rgb).astype('uint8') + # self.pixel_array[covered] = rgb - def display_points(self, points, rgbs, thickness): + + def display_vectorized(self, vect_mobjects): + im = Image.fromarray(self.pixel_array, mode = "RGB") + canvas = aggdraw.Draw(im) + for mob in vect_mobjects: + pen, fill = self.get_pen_and_fill(mob) + #TODO, fill + pathstring = self.get_pathstring( + self.points_to_pixel_coords(mob.points), + closed = mob.is_closed() + ) + symbol = aggdraw.Symbol(pathstring) + canvas.symbol((0, 0), symbol, pen, fill) + canvas.flush() + self.pixel_array = np.array(im) + + def get_pen_and_fill(self, vect_mobject): + pen = aggdraw.Pen( + vect_mobject.get_color().get_web(), + vect_mobject.point_thickness + ) + fill = aggdraw.Brush( + vect_mobject.get_fill_color().get_web(), + opacity = int(255*vect_mobject.get_fill_opacity()) + ) + return (pen, fill) + + + + def get_pathstring(self, cubic_bezier_points, closed = False): + start = "m%d,%d"%tuple(cubic_bezier_points[0]) + #(handle1, handle2, anchor) tripletes + triplets = zip(*[ + cubic_bezier_points[i+1::3] + for i in range(3) + ]) + cubics = [ + "C" + ",".join(map(str, it.chain(*triplet))) + for triplet in triplets + ] + end = "z" if closed else "" + return " ".join([start] + cubics + [end]) + + def display_point_cloud(self, points, rgbs, thickness): if len(points) == 0: return points = self.align_points_to_camera(points) diff --git a/fluid_flow.py b/fluid_flow.py deleted file mode 100644 index ea45d729..00000000 --- a/fluid_flow.py +++ /dev/null @@ -1,462 +0,0 @@ -from mobject import Mobject -from mobject.tex_mobject import TextMobject -from mobject.region import region_from_polygon_vertices -from topics.geometry import Arrow, Dot, Circle, Line, FilledRectangle -from topics.number_line import NumberPlane, XYZAxes -from topics.three_dimensions import Sphere -from scene import Scene -from animation.simple_animations import \ - ShowCreation, Rotating, PhaseFlow, ApplyToCenters -from animation.transform import \ - Transform, ApplyMethod, FadeOut, ApplyFunction - -from helpers import * - - -class FluidFlow(Scene): - CONFIG = { - "arrow_spacing" : 1, - "dot_spacing" : 0.5, - "dot_color" : BLUE_C, - "text_color" : WHITE, - "arrow_color" : GREEN_A, - "arrow_length" : 0.5, - "points_height" : SPACE_HEIGHT, - "points_width" : SPACE_WIDTH, - } - def use_function(self, function): - self.function = function - - def get_points(self, spacing): - x_radius, y_radius = [ - val-val%spacing - for val in self.points_width, self.points_height - ] - return map(np.array, it.product( - np.arange(-x_radius, x_radius+spacing, spacing), - np.arange(-y_radius, y_radius+spacing, spacing), - [0] - )) - - - def add_plane(self): - self.add(NumberPlane().fade()) - - def add_dots(self): - points = self.get_points(self.dot_spacing) - self.dots = Mobject(*map(Dot, points)) - self.dots.highlight(self.dot_color) - self.play(ShowCreation(self.dots)) - self.dither() - - def add_arrows(self, true_length = False): - if not hasattr(self, "function"): - raise Exception("Must run use_function first") - points = self.get_points(self.arrow_spacing) - points = filter( - lambda p : np.linalg.norm(self.function(p)) > 0.01, - points - ) - angles = map(angle_of_vector, map(self.function, points)) - prototype = Arrow( - ORIGIN, RIGHT*self.arrow_length, - color = self.arrow_color, - tip_length = 0.1, - buff = 0 - ) - arrows = [] - for point in points: - arrow = prototype.copy() - output = self.function(point) - if true_length: - arrow.scale(np.linalg.norm(output)) - arrow.rotate(angle_of_vector(output)) - arrow.shift(point) - arrows.append(arrow) - self.arrows = Mobject(*arrows) - - self.play(ShowCreation(self.arrows)) - self.dither() - - def add_paddle(self): - pass - - def flow(self, **kwargs): - if not hasattr(self, "function"): - raise Exception("Must run use_function first") - self.play(ApplyToCenters( - PhaseFlow, - self.dots.split(), - function = self.function, - **kwargs - )) - - def label(self, text, time = 5): - mob = TextMobject(text) - mob.scale(1.5) - mob.to_edge(UP) - rectangle = region_from_polygon_vertices(*[ - mob.get_corner(vect) + 0.3*vect - for vect in [ - UP+RIGHT, - UP+LEFT, - DOWN+LEFT, - DOWN+RIGHT - ] - ]) - mob.highlight(self.text_color) - rectangle = MobjectFromRegion(rectangle, "#111111") - rectangle.point_thickness = 3 - self.add(rectangle, mob) - self.dither(time) - self.remove(mob, rectangle) - - -class FluxArticleExample(FluidFlow): - CONFIG = { - "arrow_length" : 0.4, - "arrow_color" : BLUE_D, - "points_height" : SPACE_HEIGHT, - "points_width" : SPACE_WIDTH, - } - def construct(self): - self.use_function( - lambda (x, y, z) : (x**2+y**2)*((np.sin(x)**2)*RIGHT + np.cos(y)*UP) - ) - # self.add_plane() - self.add_arrows() - self.show_frame() - self.add_dots() - self.flow(run_time = 2, virtual_time = 0.1) - self.dither(2) - -class NegativeDivergenceExamlpe(FluidFlow): - CONFIG = { - "points_width" : 2*SPACE_WIDTH, - "points_height" : 2*SPACE_HEIGHT, - } - def construct(self): - circle = Circle(color = YELLOW_C) - self.use_function( - lambda p : -p/(2*np.linalg.norm(0.5*p)**0.5+0.01) - ) - self.add_plane() - self.add(circle) - self.add_arrows() - self.add_dots() - self.flow(run_time = 2, virtual_time = 2) - self.dither(2) - - -class PositiveDivergenceExample(FluidFlow): - def construct(self): - circle = Circle(color = YELLOW_C) - self.use_function( - lambda p : p/(2*np.linalg.norm(0.5*p)**0.5+0.01) - ) - self.add_plane() - self.add(circle) - self.add_arrows() - self.add_dots() - self.flow(run_time = 2, virtual_time = 2) - self.dither(2) - -class DivergenceArticleExample(FluidFlow): - def construct(self): - def raw_function((x, y, z)): - return (2*x-y, y*y, 0) - def normalized_function(p): - result = raw_function(p) - return result/(np.linalg.norm(result)+0.01) - self.use_function(normalized_function) - - self.add_plane() - self.add_arrows() - self.add_dots() - self.flow( - virtual_time = 4, - run_time = 5 - ) - -class QuadraticField(FluidFlow): - def construct(self): - self.use_function( - lambda (x, y, z) : 0.25*((x*x-y*y)*RIGHT+x*y*UP) - ) - self.add_plane() - self.add_arrows() - self.add_dots() - self.flow( - virtual_time = 10, - run_time = 20, - rate_func = None - ) - - -class IncompressibleFluid(FluidFlow): - CONFIG = { - "points_width" : 2*SPACE_WIDTH, - "points_height" : 1.4*SPACE_HEIGHT - } - def construct(self): - self.use_function( - lambda (x, y, z) : RIGHT+np.sin(x)*UP - ) - self.add_plane() - self.add_arrows() - self.add_dots() - for x in range(8): - self.flow( - run_time = 1, - rate_func = None, - ) - - - -class ConstantInwardFlow(FluidFlow): - CONFIG = { - "points_height" : 3*SPACE_HEIGHT, - "points_width" : 3*SPACE_WIDTH, - } - def construct(self): - self.use_function( - lambda p : -3*p/(np.linalg.norm(p)+0.1) - ) - self.add_plane() - self.add_arrows() - self.add_dots() - for x in range(4): - self.flow( - run_time = 5, - rate_func = None, - ) - - - - -class ConstantOutwardFlow(FluidFlow): - def construct(self): - self.use_function( - lambda p : p/(2*np.linalg.norm(0.5*p)**0.5+0.01) - ) - self.add_plane() - self.add_arrows() - self.add_dots() - for x in range(4): - self.flow(rate_func = None) - dot = self.dots.split()[0].copy() - dot.center() - new_dots = [ - dot.copy().shift(0.5*vect) - for vect in [ - UP, DOWN, LEFT, RIGHT, - UP+RIGHT, UP+LEFT, DOWN+RIGHT, DOWN+LEFT - ] - ] - self.dots.add(*new_dots) - - -class ConstantPositiveCurl(FluidFlow): - CONFIG = { - "points_height" : SPACE_WIDTH, - } - def construct(self): - self.use_function( - lambda p : 0.5*(-p[1]*RIGHT+p[0]*UP) - ) - self.add_plane() - self.add_arrows(true_length = True) - self.add_dots() - for x in range(10): - self.flow( - rate_func = None - ) - - - -class ComplexCurlExample(FluidFlow): - def construct(self): - self.use_function( - lambda (x, y, z) : np.cos(x+y)*RIGHT+np.sin(x*y)*UP - ) - self.add_plane() - self.add_arrows(true_length = True) - self.add_dots() - for x in range(4): - self.flow( - run_time = 5, - rate_func = None, - ) - -class SingleSwirl(FluidFlow): - CONFIG = { - "points_height" : SPACE_WIDTH, - } - def construct(self): - self.use_function( - lambda p : (-p[1]*RIGHT+p[0]*UP)/np.linalg.norm(p) - ) - self.add_plane() - self.add_arrows() - self.add_dots() - for x in range(10): - self.flow(rate_func = None) - - -class CurlArticleExample(FluidFlow): - CONFIG = { - "points_height" : 3*SPACE_HEIGHT, - "points_width" : 3*SPACE_WIDTH - } - def construct(self): - self.use_function( - lambda (x, y, z) : np.cos(0.5*(x+y))*RIGHT + np.sin(0.25*x*y)*UP - ) - circle = Circle().shift(3*UP) - self.add_plane() - self.add_arrows() - self.play(ShowCreation(circle)) - self.add_dots() - self.show_frame() - self.flow( - rate_func = None, - run_time = 15, - virtual_time = 10 - ) - - -class FourSwirlsWithoutCircles(FluidFlow): - CONFIG = { - "points_height" : SPACE_WIDTH, - } - def construct(self): - circles = [ - Circle().shift(3*vect) - for vect in compass_directions() - ] - self.use_function( - lambda (x, y, z) : 0.5*(y**3-9*y)*RIGHT+(x**3-9*x)*UP - ) - self.add_plane() - self.add_arrows() - # for circle in circles: - # self.play(ShowCreation(circle)) - self.add_dots() - self.add_extra_dots() - self.flow( - virtual_time = 4, - run_time = 20, - rate_func = None - ) - - def add_extra_dots(self): - dots = self.dots.split() - for vect in UP+LEFT, DOWN+RIGHT: - for n in range(5, 15): - dots.append( - dots[0].copy().center().shift(n*vect) - ) - self.dots = Mobject(*dots) - - -class CopyPlane(Scene): - def construct(self): - def special_rotate(mob): - mob.rotate(0.9*np.pi/2, RIGHT) - mob.rotate(-np.pi/4, UP) - return mob - plane = NumberPlane() - copies = [ - special_rotate(plane.copy().shift(u*n*OUT)) - for n in range(1, 3) - for u in -1, 1 - ] - line = Line(4*IN, 4*OUT) - - - self.add(plane) - self.play(*[ - ApplyFunction(special_rotate, mob, run_time = 3) - for mob in plane, line - ]) - self.dither() - for copy in copies: - self.play(Transform(plane.copy(), copy)) - self.dither() - - -class Test3DMovement(Scene): - def construct(self): - axes = XYZAxes() - axes.highlight(WHITE) - plane = NumberPlane() - vects = [ - Arrow(point, point+(3./27)*(3*x**2-3*y**2)*OUT, color = MAROON_D) - for x in range(-4, 5, 2) - for y in range(-5, 5, 2) - for point in [x*RIGHT + y*UP] - ] - everybody = Mobject(axes, plane, *vects) - - self.play(ApplyMethod( - everybody.rotate, 0.9*np.pi/2, RIGHT - )) - self.dither() - self.play(ApplyMethod( - everybody.rotate, - np.pi/2, - run_time = 5 - )) - - - -class DropletFlow(FluidFlow): - def construct(self): - seconds = 20 - droplets = Mobject(*[ - Dot(x*RIGHT+y*UP, color = BLUE_D) - for x in range(-7, 9) - for y in range(-3, 4) - ]) - self.use_function( - lambda (x, y, z) : 0.5*(y**3-9*y)*RIGHT+(x**3-9*x)*UP, - ) - self.add_arrows() - self.play(ShowCreation(droplets)) - for x in range(seconds): - self.play(PhaseFlow( - self.function, - droplets, - virtual_time = 1./10, - rate_func = None, - )) - droplets.add(*[ - Dot(5*vect, color = BLUE_D) - for vect in UP+LEFT, DOWN+RIGHT - ]) - -class AltDropletFlow(FluidFlow): - def construct(self): - self.use_function(lambda (x, y, z): - (np.sin(x)+np.sin(y))*RIGHT+\ - (np.sin(x)-np.sin(y))*UP - ) - self.add_dots() - self.flow( - rate_func = None, - run_time = 10, - virtual_time = 2 - ) - - - - - - - - - - - - - diff --git a/helpers.py b/helpers.py index b1ea06ba..74a909fd 100644 --- a/helpers.py +++ b/helpers.py @@ -12,8 +12,11 @@ import re from constants import * +def color_to_rgb(color): + return np.array(Color(color).get_rgb()) + def color_to_int_rgb(color): - return (255*np.array(Color(color).get_rgb())).astype('uint8') + return (255*color_to_rgb(color)).astype('uint8') def compass_directions(n = 4, start_vect = UP): angle = 2*np.pi/n diff --git a/mobject/__init__.py b/mobject/__init__.py index 14733f40..98c80109 100644 --- a/mobject/__init__.py +++ b/mobject/__init__.py @@ -4,4 +4,6 @@ __all__ = [ "tex_mobject", ] -from mobject import Mobject, Point, Mobject1D, Mobject2D \ No newline at end of file +from mobject import Mobject +from point_cloud_mobject import Point, Mobject1D, Mobject2D, PointCloudMobject +from vectorized_mobject import VectorizedMobject \ No newline at end of file diff --git a/mobject/mobject.py b/mobject/mobject.py index bee75731..5d9c5445 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -8,76 +8,51 @@ from colour import Color from helpers import * +#TODO: Explain array_attrs + class Mobject(object): """ Mathematical Object + """ #Number of numbers used to describe a point (3 for pos, 3 for normal vector) CONFIG = { "color" : WHITE, "point_thickness" : DEFAULT_POINT_THICKNESS, "name" : None, - "display_mode" : "points" + "display_mode" : "points", #TODO, REMOVE + "dim" : 3, } - DIM = 3 def __init__(self, *sub_mobjects, **kwargs): digest_config(self, kwargs) self.sub_mobjects = list(sub_mobjects) self.color = Color(self.color) if self.name is None: self.name = self.__class__.__name__ - self.has_normals = hasattr(self, 'unit_normal') self.init_points() + self.init_colors() self.generate_points() - if self.has_normals: - self.unit_normals = np.apply_along_axis( - self.unit_normal, - 1, - self.points, - ) def __str__(self): return self.name def init_points(self): - for attr in self.get_array_attrs(): - setattr(self, attr, np.zeros((0, 3))) + self.points = np.zeros((0, self.dim)) + + def init_colors(self): + #For subclasses + pass def generate_points(self): #Typically implemented in subclass, unless purposefully left blank pass - def add_points(self, points, rgbs = None, color = None): - """ - points must be a Nx3 numpy array, as must rgbs if it is not None - """ - if not isinstance(points, np.ndarray): - points = np.array(points) - num_new_points = points.shape[0] - self.points = np.append(self.points, points, axis = 0) - if rgbs is None: - color = Color(color) if color else self.color - rgbs = np.array([color.get_rgb()] * num_new_points) - elif rgbs.shape != points.shape: - raise Exception("points and rgbs must have same shape") - self.rgbs = np.append(self.rgbs, rgbs, axis = 0) - if self.has_normals: - self.unit_normals = np.append( - self.unit_normals, - np.apply_along_axis(self.unit_normal, 1, points), - axis = 0 - ) - return self - def add(self, *mobjects): self.sub_mobjects = list_update(self.sub_mobjects, mobjects) return self def get_array_attrs(self): - result = ["points", "rgbs"] - if self.has_normals: - result.append("unit_normals") - return result + return ["points"] def digest_mobject_attrs(self): """ @@ -91,33 +66,40 @@ class Mobject(object): self.sub_mobjects = list_update(self.sub_mobjects, mobject_attrs) return self - def apply_over_attr_arrays(self, func): for attr in self.get_array_attrs(): setattr(self, attr, func(getattr(self, attr))) return self - def show(self): + def get_image(self): from camera import Camera camera = Camera() camera.capture_mobject(self) - Image.fromarray(camera.get_image()).show() + return Image.fromarray(camera.get_image()) + + def show(self): + self.get_image().show() def save_image(self, name = None): - Image.fromarray(disp.paint_mobject(self)).save( + self.get_image().save( os.path.join(MOVIE_DIR, (name or str(self)) + ".png") ) def copy(self): return deepcopy(self) - #### Fundamental operations ###### + #### Transforming operations ###### + + def apply_to_family(self, func): + for mob in self.nonempty_family_members(): + func(mob) def shift(self, *vectors): total_vector = reduce(op.add, vectors) for mob in self.nonempty_family_members(): - mob.points += total_vector - return self + mob.points += total_vector + return self + def scale(self, scale_factor): for mob in self.nonempty_family_members(): @@ -133,8 +115,6 @@ class Mobject(object): t_rot_matrix = np.transpose(rot_matrix) for mob in self.nonempty_family_members(): mob.points = np.dot(mob.points, t_rot_matrix) - if mob.has_normals: - mob.unit_normals = np.dot(mob.unit_normals, t_rot_matrix) return self def stretch(self, factor, dim): @@ -159,68 +139,6 @@ class Mobject(object): ) return self - def highlight(self, color = YELLOW_C, condition = None): - """ - Condition is function which takes in one arguments, (x, y, z). - """ - rgb = Color(color).get_rgb() - for mob in self.nonempty_family_members(): - if condition: - to_change = np.apply_along_axis(condition, 1, mob.points) - mob.rgbs[to_change, :] = rgb - else: - mob.rgbs[:,:] = rgb - return self - - def gradient_highlight(self, start_color, end_color): - start_rgb, end_rgb = [ - np.array(Color(color).get_rgb()) - for color in start_color, end_color - ] - for mob in self.nonempty_family_members(): - num_points = mob.get_num_points() - mob.rgbs = np.array([ - interpolate(start_rgb, end_rgb, alpha) - for alpha in np.arange(num_points)/float(num_points) - ]) - return self - - def match_colors(self, mobject): - Mobject.align_data(self, mobject) - self.rgbs = np.array(mobject.rgbs) - return self - - def filter_out(self, condition): - for mob in self.nonempty_family_members(): - to_eliminate = ~np.apply_along_axis(condition, 1, mob.points) - mob.points = mob.points[to_eliminate] - mob.rgbs = mob.rgbs[to_eliminate] - return self - - def thin_out(self, factor = 5): - """ - Removes all but every nth point for n = factor - """ - for mob in self.nonempty_family_members(): - num_points = self.get_num_points() - mob.apply_over_attr_arrays( - lambda arr : arr[ - np.arange(0, num_points, factor) - ] - ) - return self - - def sort_points(self, function = lambda p : p[0]): - """ - function is any map from R^3 to R - """ - for mob in self.nonempty_family_members(): - indices = np.argsort( - np.apply_along_axis(function, 1, mob.points) - ) - mob.apply_over_attr_arrays(lambda arr : arr[indices]) - return self - def reverse_points(self): for mob in self.nonempty_family_members(): mob.apply_over_attr_arrays( @@ -228,7 +146,6 @@ class Mobject(object): ) return self - def repeat(self, count): """ This can make transition animations nicer @@ -336,11 +253,16 @@ class Mobject(object): self.shift(start-self.points[0]) return self + ## Color functions - def apply_complex_function(self, function): - return self.apply_function( - lambda (x, y, z) : complex_to_R3(function(complex(x, y))) - ) + def highlight(self, color = YELLOW_C, condition = None): + """ + Condition is function which takes in one arguments, (x, y, z). + """ + raise Exception("Not implemented") + + def gradient_highlight(self, start_color, end_color): + raise Exception("Not implemented") def set_color(self, color): self.highlight(color) @@ -352,15 +274,27 @@ class Mobject(object): return self def fade_to(self, color, alpha): - self.rgbs = interpolate(self.rgbs, np.array(Color(color).rgb), alpha) - for mob in self.sub_mobjects: - mob.fade_to(color, alpha) + start = color_to_rgb(self.get_color()) + end = color_to_rgb(color) + new_rgb = interpolate(start, end, alpha) + for mob in self.nonempty_family_members(): + mob.highlight(Color(rgb = new_rgb)) return self def fade(self, darkness = 0.5): self.fade_to(BLACK, darkness) return self + def get_color(self): + return self.color + ## + + + def apply_complex_function(self, function): + return self.apply_function( + lambda (x, y, z) : complex_to_R3(function(complex(x, y))) + ) + def reduce_across_dimension(self, points_func, reduce_func, dim): try: values = [points_func(self.points[:, dim])] @@ -384,32 +318,6 @@ class Mobject(object): def get_all_points(self): return self.get_merged_array("points") - def get_all_rgbs(self): - return self.get_merged_array("rgbs") - - def ingest_sub_mobjects(self): - attrs = self.get_array_attrs() - arrays = map(self.get_merged_array, attrs) - for attr, array in zip(attrs, arrays): - setattr(self, attr, array) - self.sub_mobjects = [] - return self - - def split(self): - result = [self] if len(self.points) > 0 else [] - return result + self.sub_mobjects - - def submobject_family(self): - sub_families = map(Mobject.submobject_family, self.sub_mobjects) - all_mobjects = [self] + reduce(op.add, sub_families, []) - return remove_list_redundancies(all_mobjects) - - def nonempty_family_members(self): - return filter( - lambda m : m.get_num_points() > 0, - self.submobject_family() - ) - ### Getters ### def get_num_points(self, including_submobjects = False): @@ -476,13 +384,27 @@ class Mobject(object): return self.length_over_dim(1) def point_from_proportion(self, alpha): - index = alpha*(self.get_num_points()-1) - return self.points[index] + raise Exception("Not implemented") - def get_color(self): - color = Color() - color.set_rgb(self.rgbs[0, :]) - return color + + ## Family matters + + def split(self): + result = [self] if len(self.points) > 0 else [] + return result + self.sub_mobjects + + def submobject_family(self): + sub_families = map(Mobject.submobject_family, self.sub_mobjects) + all_mobjects = [self] + reduce(op.add, sub_families, []) + return remove_list_redundancies(all_mobjects) + + def nonempty_family_members(self): + return filter( + lambda m : m.get_num_points() > 0, + self.submobject_family() + ) + + ## Alignment @staticmethod def align_data(mobject1, mobject2): @@ -527,52 +449,7 @@ class Mobject(object): setattr(self, attr, interpolate( getattr(mobject1, attr), getattr(mobject2, attr), - alpha)) - - -class Point(Mobject): - CONFIG = { - "color" : BLACK, - } - def __init__(self, location = ORIGIN, **kwargs): - digest_locals(self) - Mobject.__init__(self, **kwargs) - - def generate_points(self): - self.add_points([self.location]) - -#TODO, Make the two implementations bellow non-redundant -class Mobject1D(Mobject): - CONFIG = { - "density" : DEFAULT_POINT_DENSITY_1D, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - self.epsilon = 1.0 / self.density - Mobject.__init__(self, **kwargs) - - - def add_line(self, start, end, color = None): - start, end = map(np.array, [start, end]) - length = np.linalg.norm(end - start) - if length == 0: - points = [start] - else: - epsilon = self.epsilon/length - points = [ - interpolate(start, end, t) - for t in np.arange(0, 1, epsilon) - ] - self.add_points(points, color = color) - -class Mobject2D(Mobject): - CONFIG = { - "density" : DEFAULT_POINT_DENSITY_2D, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - self.epsilon = 1.0 / self.density - Mobject.__init__(self, **kwargs) + alpha)) diff --git a/mobject/point_cloud_mobject.py b/mobject/point_cloud_mobject.py new file mode 100644 index 00000000..93c026f9 --- /dev/null +++ b/mobject/point_cloud_mobject.py @@ -0,0 +1,158 @@ +from .mobject import Mobject +from helpers import * + +class PointCloudMobject(Mobject): + def init_colors(self): + self.rgbs = np.zeros((0, 3)) + return self + + def get_array_attrs(self): + return Mobject.get_array_attrs(self) + ["rgbs"] + + + def add_points(self, points, rgbs = None, color = None): + """ + points must be a Nx3 numpy array, as must rgbs if it is not None + """ + if not isinstance(points, np.ndarray): + points = np.array(points) + num_new_points = points.shape[0] + self.points = np.append(self.points, points, axis = 0) + if rgbs is None: + color = Color(color) if color else self.color + rgbs = np.array([color.get_rgb()] * num_new_points) + elif rgbs.shape != points.shape: + raise Exception("points and rgbs must have same shape") + self.rgbs = np.append(self.rgbs, rgbs, axis = 0) + return self + + def highlight(self, color = YELLOW_C, condition = None): + rgb = Color(color).get_rgb() + for mob in self.nonempty_family_members(): + if condition: + to_change = np.apply_along_axis(condition, 1, mob.points) + mob.rgbs[to_change, :] = rgb + else: + mob.rgbs[:,:] = rgb + return self + + def gradient_highlight(self, start_color, end_color): + start_rgb, end_rgb = [ + np.array(Color(color).get_rgb()) + for color in start_color, end_color + ] + for mob in self.nonempty_family_members(): + num_points = mob.get_num_points() + mob.rgbs = np.array([ + interpolate(start_rgb, end_rgb, alpha) + for alpha in np.arange(num_points)/float(num_points) + ]) + return self + + + def match_colors(self, mobject): + Mobject.align_data(self, mobject) + self.rgbs = np.array(mobject.rgbs) + return self + + def filter_out(self, condition): + for mob in self.nonempty_family_members(): + to_eliminate = ~np.apply_along_axis(condition, 1, mob.points) + mob.points = mob.points[to_eliminate] + mob.rgbs = mob.rgbs[to_eliminate] + return self + + def thin_out(self, factor = 5): + """ + Removes all but every nth point for n = factor + """ + for mob in self.nonempty_family_members(): + num_points = self.get_num_points() + mob.apply_over_attr_arrays( + lambda arr : arr[ + np.arange(0, num_points, factor) + ] + ) + return self + + def sort_points(self, function = lambda p : p[0]): + """ + function is any map from R^3 to R + """ + for mob in self.nonempty_family_members(): + indices = np.argsort( + np.apply_along_axis(function, 1, mob.points) + ) + mob.apply_over_attr_arrays(lambda arr : arr[indices]) + return self + + def fade_to(self, color, alpha): + self.rgbs = interpolate(self.rgbs, np.array(Color(color).rgb), alpha) + for mob in self.sub_mobjects: + mob.fade_to(color, alpha) + return self + + def get_all_rgbs(self): + return self.get_merged_array("rgbs") + + def ingest_sub_mobjects(self): + attrs = self.get_array_attrs() + arrays = map(self.get_merged_array, attrs) + for attr, array in zip(attrs, arrays): + setattr(self, attr, array) + self.sub_mobjects = [] + return self + + def get_color(self): + return Color(rgb = self.rgbs[0, :]) + + def point_from_proportion(self, alpha): + index = alpha*(self.get_num_points()-1) + return self.points[index] + + +#TODO, Make the two implementations bellow non-redundant +class Mobject1D(PointCloudMobject): + CONFIG = { + "density" : DEFAULT_POINT_DENSITY_1D, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + self.epsilon = 1.0 / self.density + Mobject.__init__(self, **kwargs) + + + def add_line(self, start, end, color = None): + start, end = map(np.array, [start, end]) + length = np.linalg.norm(end - start) + if length == 0: + points = [start] + else: + epsilon = self.epsilon/length + points = [ + interpolate(start, end, t) + for t in np.arange(0, 1, epsilon) + ] + self.add_points(points, color = color) + +class Mobject2D(PointCloudMobject): + CONFIG = { + "density" : DEFAULT_POINT_DENSITY_2D, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + self.epsilon = 1.0 / self.density + Mobject.__init__(self, **kwargs) + + + +class Point(Mobject): + CONFIG = { + "color" : BLACK, + } + def __init__(self, location = ORIGIN, **kwargs): + digest_locals(self) + Mobject.__init__(self, **kwargs) + + def generate_points(self): + self.add_points([self.location]) diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py new file mode 100644 index 00000000..4410da8a --- /dev/null +++ b/mobject/vectorized_mobject.py @@ -0,0 +1,179 @@ +from scipy import linalg + +from .mobject import Mobject + +from helpers import * + +class VectorizedMobject(Mobject): + CONFIG = { + "closed" : False, + "fill_color" : BLACK, + "fill_opacity" : 0.0 + } + + ## Colors + def init_colors(self): + self.set_stroke_color(self.color) + self.set_fill_color(self.fill_color) + return self + + def set_fill_color(self, color): + self.fill_rgb = color_to_rgb(color) + return self + + def set_stroke_color(self, color): + self.stroke_rgb = color_to_rgb(color) + + def highlight(self, color): + self.set_fill_color(color) + self.set_stroke_color(color) + return self + + def get_fill_color(self): + return Color(rgb = self.fill_rgb) + + def get_fill_opacity(self): + return self.fill_opacity + + def get_storke_color(self): + return Color(rgb = self.stroke_rgb) + + #TODO, get color? Specify if stroke or fill + #is the predominant color attribute? + + ## Drawing + def init_points(self): + ##Default to starting at origin + self.points = np.zeros((1, self.dim)) + return self + + def start_at(self, point): + self.points[0] = point + return self + + def close(self): + self.closed = True + return self + + def open(self): + self.closed = False + return self + + def is_closed(self): + return self.closed + + def add_point(self, handle1, handle2, point): + self.points = np.append( + self.points, + [handle1, handle2, point], + axis = 0 + ) + return self + + def set_anchors_and_handles(self, anchors, handles1, handles2): + assert(len(anchors) == len(handles1)+1) + assert(len(anchors) == len(handles2)+1) + total_len = 3*(len(anchors)-1) + 1 + self.points = np.zeros((total_len, self.dim)) + self.points[0] = anchors[0] + arrays = [handles1, handles2, anchors[1:]] + for index, array in zip(it.count(1), arrays): + self.points[index::3] = array + return self.points + + def get_anchors_and_handles(self): + return [ + self.points[i::3] + for i in range(3) + ] + + def set_points_as_corners(self, points): + handles1 = points[:-1] + handles2 = points[1:] + self.set_anchors_and_handles(points, handles1, handles2) + return self + + def set_points_smoothly(self, points): + if self.is_closed(): + points = np.append( + points, + [points[0], points[1]], + axis = 0 + ) + num_handles = len(points) - 1 + #Must solve 2*num_handles equations to get the handles. + #l and u are the number of lower an upper diagonal rows + #in the matrix to solve. + l, u = 2, 1 + #diag is a representation of the matrix in diagonal form + #See https://www.particleincell.com/2012/bezier-splines/ + #for how to arive at these equations + diag = np.zeros((l+u+1, 2*num_handles)) + diag[0,1::2] = -1 + diag[0,2::2] = 1 + diag[1,0::2] = 2 + diag[1,1::2] = 1 + diag[2,1:-2:2] = -2 + diag[3,0:-3:2] = 1 + #This is the b as in Ax = b, where we are solving for x, + #and A is represented using diag. However, think of entries + #to x and b as being points in space, not numbers + b = np.zeros((2*num_handles, self.dim)) + b[1::2] = 2*points[1:] + b[0] = points[0] + b[-1] = points[-1] + + handle_pairs = np.zeros((2*num_handles, self.dim)) + for i in range(self.dim): + handle_pairs[:,i] = linalg.solve_banded( + (l, u), diag, b[:,i] + ) + handles1 = handle_pairs[0::2] + handles2 = handle_pairs[1::2] + if self.is_closed(): + #Ignore last point that was artificially added + #to smooth out the closing. + #TODO, is the the best say to handle this? + handles1[0] = handles1[-1] + points = points[:-1] + handles1 = handles1[:-1] + handles2 = handles2[:-1] + + self.set_anchors_and_handles(points, handles1, handles2) + + + def set_points(self, points, mode = "smooth"): + points = np.array(points) + if mode == "smooth": + self.set_points_smoothly(points) + elif mode == "corners": + self.set_points_as_corners(points) + elif mode == "handles_included": + self.points = points + else: + raise Exception("Unknown mode") + return self + + ## Information about line + + def get_num_points(self): + pass + + def point_from_proportion(self, alpha): + pass + + + + + + + + + + + + + + + +