diff --git a/brachistochrone/curves.py b/brachistochrone/curves.py index 5e4459eb..ccc410ac 100644 --- a/brachistochrone/curves.py +++ b/brachistochrone/curves.py @@ -203,8 +203,12 @@ class PathSlidingScene(Scene): 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) @@ -594,10 +598,112 @@ class WhatGovernsSpeed(PathSlidingScene): - - - - +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 index 5e1379e7..6cac54c9 100644 --- a/brachistochrone/cycloid.py +++ b/brachistochrone/cycloid.py @@ -502,34 +502,61 @@ class LeviSolution(CycloidScene): class EquationsForCycloid(CycloidScene): def construct(self): CycloidScene.construct(self) - equations = TexMobject(""" - x(t) &= Rt - R\\sin(t) \\\\ - y(t) &= -R + R\\cos(t) - """) - equations.shift(2*UP) + 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 } - def construct(self): + + 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 ) - self.play(ShowCreation(self.cycloid)) + 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() @@ -538,12 +565,26 @@ class SlidingObject(CycloidScene, PathSlidingScene): self.dither() self.roll_back() self.dither() - radial_line = self.circle.sub_mobjects[0] - self.circle.add(self.slider) - self.circle.get_center = lambda : radial_line.get_start_and_end()[0] - self.draw_cycloid() + 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() - diff --git a/brachistochrone/drawing_images.py b/brachistochrone/drawing_images.py index 39fb3cdf..0173a5bb 100644 --- a/brachistochrone/drawing_images.py +++ b/brachistochrone/drawing_images.py @@ -317,28 +317,28 @@ class JohannThinksOfFermat(Scene): class MathematiciansOfEurope(Scene): def construct(self): - europe = ImageMobject("1700_Europe", invert = False) + europe = ImageMobject("Europe", use_cache = False) self.add(europe) self.freeze_background() mathematicians = [ - ("Newton", [-1.6, 0.6, 0]), - ("Jacob_Bernoulli",[-1, -0.75, 0]), - ("Ehrenfried_von_Tschirnhaus",[-0.5, 0.2, 0]), - ("Gottfried_Wilhelm_von_Leibniz",[-0.1, -0.75, 0]), - ("Guillaume_de_L'Hopital", [-1.5, -0.5, 0]), + ("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( - DelayByOrder(Transform(mobject, man)), - ShimmerIn(name_mob) - ) + self.play(Transform(mobject, man)) man.scale(0.2) man.shift(point) self.play(Transform(mobject, man)) diff --git a/brachistochrone/light.py b/brachistochrone/light.py index 63c54858..68ac91de 100644 --- a/brachistochrone/light.py +++ b/brachistochrone/light.py @@ -878,7 +878,59 @@ class WhichPathWouldLightTake(PhotonScene, TryManyPaths): +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 index e1626737..2f4cbf45 100644 --- a/brachistochrone/misc.py +++ b/brachistochrone/misc.py @@ -17,8 +17,10 @@ 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): @@ -232,9 +234,266 @@ class StayedUpAllNight(Scene): self.dither() -class ThetaTSigmoidGraph(Scene): +class ThetaTGraph(Scene): def construct(self): - pass + 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) @@ -249,7 +508,5 @@ class ThetaTSigmoidGraph(Scene): - - diff --git a/brachistochrone/multilayered.py b/brachistochrone/multilayered.py index 76689a0b..a1d7ff72 100644 --- a/brachistochrone/multilayered.py +++ b/brachistochrone/multilayered.py @@ -387,7 +387,7 @@ class ContinuouslyObeyingSnellsLaw(MultilayeredScene): def snells_law_at_every_point(self, cycloid, chopped_cycloid): square = Square(side_length = 0.2, color = WHITE) - words = TextMobject(["Snell's law ", " at every point"]) + words = TextMobject(["Snell's law ", "everywhere"]) snells, rest = words.split() colon = TextMobject(":") words.next_to(square) diff --git a/brachistochrone/wordplay.py b/brachistochrone/wordplay.py index c0d9783d..d61d31c0 100644 --- a/brachistochrone/wordplay.py +++ b/brachistochrone/wordplay.py @@ -469,6 +469,100 @@ class BalanceCompetingFactors(Scene): +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/scene/scene.py b/scene/scene.py index 0d01b22e..a9be4bdf 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -188,7 +188,6 @@ class Scene(object): self.frames[index] = self.get_frame() for animation in animations: animation.clean_up() - self.repaint_mojects() return self def dither(self, duration = DEFAULT_DITHER_TIME): diff --git a/topics/characters.py b/topics/characters.py index ff578966..62bcdf34 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -130,8 +130,6 @@ class Mortimer(PiCreature): } def __init__(self, **kwargs): PiCreature.__init__(self, **kwargs) - # self.highlight(DARK_BROWN) - self.give_straight_face() self.rotate(np.pi, UP) @@ -139,10 +137,6 @@ class Mathematician(PiCreature): CONFIG = { "color" : GREY, } - def __init__(self, **kwargs): - PiCreature.__init__(self, **kwargs) - self.give_straight_face() - class Bubble(Mobject): CONFIG = {