from manimlib.imports import * from old_projects.div_curl import VectorField from old_projects.div_curl import get_force_field_func COBALT = "#0047AB" # Warning, this file uses ContinualChangingDecimal, # which has since been been deprecated. Use a mobject # updater instead # TODO, this is untested after turning it from a # ContinualAnimation into a VGroup class Orbiting(VGroup): CONFIG = { "rate": 7.5, } def __init__(self, planet, star, ellipse, **kwargs): VGroup.__init__(self, **kwargs) self.add(planet) self.planet = planet self.star = star self.ellipse = ellipse # Proportion of the way around the ellipse self.proportion = 0 planet.move_to(ellipse.point_from_proportion(0)) self.add_updater(lambda m, dt: m.update(dt)) def update(self, dt): # time = self.internal_time planet = self.planet star = self.star ellipse = self.ellipse rate = self.rate radius_vector = planet.get_center() - star.get_center() rate *= 1.0 / get_norm(radius_vector) prop = self.proportion d_prop = 0.001 ds = get_norm(op.add( ellipse.point_from_proportion((prop + d_prop) % 1), -ellipse.point_from_proportion(prop), )) delta_prop = (d_prop / ds) * rate * dt self.proportion = (self.proportion + delta_prop) % 1 planet.move_to( ellipse.point_from_proportion(self.proportion) ) # TODO, this is untested after turning it from a # ContinualAnimation into a Group class SunAnimation(Group): CONFIG = { "rate": 0.2, "angle": 60 * DEGREES, } def __init__(self, sun, **kwargs): Group.__init__(self, **kwargs) self.sun = sun self.rotated_sun = sun.deepcopy() self.rotated_sun.rotate(60 * DEGREES) self.time = 0 self.add(self.sun, self.rotated_sun) self.add_updater(lambda m, dt: m.update(dt)) def update(self, dt): time = self.time self.time += dt a = (np.sin(self.rate * time * TAU) + 1) / 2.0 self.rotated_sun.rotate(-self.angle) self.rotated_sun.move_to(self.sun) self.rotated_sun.rotate(self.angle) self.rotated_sun.pixel_array = np.array( a * self.sun.pixel_array, dtype=self.sun.pixel_array.dtype ) class ShowWord(Animation): CONFIG = { "time_per_char": 0.06, "rate_func": linear, } def __init__(self, word, **kwargs): assert(isinstance(word, SingleStringTexMobject)) digest_config(self, kwargs) run_time = kwargs.pop( "run_time", self.time_per_char * len(word) ) self.stroke_width = word.get_stroke_width() Animation.__init__(self, word, run_time=run_time, **kwargs) def interpolate_mobject(self, alpha): word = self.mobject stroke_width = self.stroke_width count = int(alpha * len(word)) remainder = (alpha * len(word)) % 1 word[:count].set_fill(opacity=1) word[:count].set_stroke(width=stroke_width) if count < len(word): word[count].set_fill(opacity=remainder) word[count].set_stroke(width=remainder * stroke_width) word[count + 1:].set_fill(opacity=0) word[count + 1:].set_stroke(width=0) # Animations class TakeOver(PiCreatureScene): CONFIG = { "default_pi_creature_kwargs": { "color": GREY_BROWN, "flip_at_start": True, }, "default_pi_creature_start_corner": DR, } def construct(self): gradient = ImageMobject("white_black_gradient") gradient.set_height(FRAME_HEIGHT) self.add(gradient) morty = self.pi_creatures henry = ImageMobject("Henry_As_Stick") henry.set_height(4) henry.to_edge(LEFT) henry.to_edge(DOWN) self.add(morty, henry) self.pi_creature_says( "Muahaha! All \\\\ mine now.", bubble_kwargs={"fill_opacity": 0.5}, bubble_creation_class=FadeIn, target_mode="conniving", added_anims=[henry.rotate, 5 * DEGREES] ) self.wait(2) class ShowEmergingEllipse(Scene): CONFIG = { "circle_radius": 3, "circle_color": BLUE, "num_lines": 150, "lines_stroke_width": 1, "eccentricity_vector": 2 * RIGHT, "ghost_lines_stroke_color": LIGHT_GREY, "ghost_lines_stroke_width": 0.5, "ellipse_color": PINK, } def construct(self): circle = self.get_circle() e_point = self.get_eccentricity_point() e_dot = Dot(e_point, color=YELLOW) lines = self.get_lines() ellipse = self.get_ellipse() fade_rect = FullScreenFadeRectangle() line = lines[len(lines) // 5] line_dot = Dot(line.get_center(), color=YELLOW) line_dot.scale(0.5) ghost_line = self.get_ghost_lines(line) ghost_lines = self.get_ghost_lines(lines) rot_words = TextMobject("Rotate $90^\\circ$ \\\\ about center") rot_words.next_to(line_dot, RIGHT) elbow = self.get_elbow(line) eccentric_words = TextMobject("``Eccentric'' point") eccentric_words.next_to(circle.get_center(), DOWN) ellipse_words = TextMobject("Perfect ellipse") ellipse_words.next_to(ellipse, UP, SMALL_BUFF) for text in rot_words, ellipse_words: text.add_to_back(text.copy().set_stroke(BLACK, 5)) shuffled_lines = VGroup(*lines) random.shuffle(shuffled_lines.submobjects) self.play(ShowCreation(circle)) self.play( FadeInFrom(e_dot, LEFT), Write(eccentric_words, run_time=1) ) self.wait() self.play( LaggedStartMap(ShowCreation, shuffled_lines), Animation(VGroup(e_dot, circle)), FadeOut(eccentric_words) ) self.add(ghost_lines) self.add(e_dot, circle) self.wait() self.play( FadeIn(fade_rect), Animation(line), GrowFromCenter(line_dot), FadeInFromDown(rot_words), ) self.wait() self.add(ghost_line) self.play( MoveToTarget(line, path_arc=90 * DEGREES), Animation(rot_words), ShowCreation(elbow) ) self.wait() self.play( FadeOut(fade_rect), FadeOut(line_dot), FadeOut(rot_words), FadeOut(elbow), Animation(line), Animation(ghost_line) ) self.play( LaggedStartMap(MoveToTarget, lines, run_time=4), Animation(VGroup(e_dot, circle)) ) self.wait() self.play( ShowCreation(ellipse), FadeInFromDown(ellipse_words) ) self.wait() def get_circle(self): circle = self.circle = Circle( radius=self.circle_radius, color=self.circle_color ) return circle def get_eccentricity_point(self): return self.circle.get_center() + self.eccentricity_vector def get_lines(self): center = self.circle.get_center() radius = self.circle.get_width() / 2 e_point = self.get_eccentricity_point() lines = VGroup(*[ Line( e_point, center + rotate_vector(radius * RIGHT, angle) ) for angle in np.linspace(0, TAU, self.num_lines) ]) lines.set_stroke(width=self.lines_stroke_width) for line in lines: line.generate_target() line.target.rotate(90 * DEGREES) return lines def get_ghost_lines(self, lines): return lines.copy().set_stroke( color=self.ghost_lines_stroke_color, width=self.ghost_lines_stroke_width ) def get_elbow(self, line): elbow = VGroup(Line(UP, UL), Line(UL, LEFT)) elbow.set_stroke(width=1) elbow.scale(0.2, about_point=ORIGIN) elbow.rotate( line.get_angle() - 90 * DEGREES, about_point=ORIGIN ) elbow.shift(line.get_center()) return elbow def get_ellipse(self): center = self.circle.get_center() e_point = self.get_eccentricity_point() radius = self.circle.get_width() / 2 # Ellipse parameters a = radius / 2 c = get_norm(e_point - center) / 2 b = np.sqrt(a**2 - c**2) result = Circle(radius=b, color=self.ellipse_color) result.stretch(a / b, 0) result.move_to(Line(center, e_point)) return result class ShowFullStory(Scene): def construct(self): directory = os.path.join( MEDIA_DIR, "animations/active_projects/lost_lecture/images" ) scene_names = [ "ShowEmergingEllipse", "ShowFullStory", "FeynmanFameStart", "TheMotionOfPlanets", "FeynmanElementaryQuote", "DrawingEllipse", "ShowEllipseDefiningProperty", "ProveEllipse", "KeplersSecondLaw", "AngularMomentumArgument", "HistoryOfAngularMomentum", "FeynmanRecountingNewton", "IntroduceShapeOfVelocities", "ShowEqualAngleSlices", "PonderOverOffCenterDiagram", "UseVelocityDiagramToDeduceCurve", ] images = Group(*[ ImageMobject(os.path.join(directory, name + ".png")) for name in scene_names ]) for image in images: image.add( SurroundingRectangle(image, buff=0, color=WHITE) ) images.arrange_in_grid(n_rows=4) images.scale( 1.01 * FRAME_WIDTH / images[0].get_width() ) images.shift(-images[0].get_center()) self.play( images.set_width, FRAME_WIDTH - 1, images.center, run_time=3, ) self.wait() self.play( images.shift, -images[2].get_center(), images.scale, FRAME_WIDTH / images[2].get_width(), {"about_point": ORIGIN}, run_time=3, ) self.wait() class FeynmanAndOrbitingPlannetOnEllipseDiagram(ShowEmergingEllipse): def construct(self): circle = self.get_circle() lines = self.get_lines() ghost_lines = self.get_ghost_lines(lines) for line in lines: MoveToTarget(line).update(1) ellipse = self.get_ellipse() e_dot = Dot(self.get_eccentricity_point()) e_dot.set_color(YELLOW) comet = ImageMobject("earth") comet.set_width(0.3) feynman = ImageMobject("Feynman") feynman.set_height(6) feynman.next_to(ORIGIN, LEFT) feynman.to_edge(UP) feynman_name = TextMobject("Richard Feynman") feynman_name.next_to(feynman, DOWN) feynman.save_state() feynman.shift(2 * DOWN) feynman_rect = BackgroundRectangle( feynman, fill_opacity=1 ) group = VGroup(circle, ghost_lines, lines, e_dot, ellipse) self.add(group) self.add(Orbiting(comet, e_dot, ellipse)) self.add_foreground_mobjects(comet) self.wait() self.play( feynman.restore, MaintainPositionRelativeTo(feynman_rect, feynman), VFadeOut(feynman_rect), group.to_edge, RIGHT, ) self.play(Write(feynman_name)) self.wait() self.wait(10) class FeynmanFame(Scene): def construct(self): books = VGroup( ImageMobject("Feynman_QED_cover"), ImageMobject("Surely_Youre_Joking_cover"), ImageMobject("Feynman_Lectures_cover"), ) for book in books: book.set_height(6) book.move_to(FRAME_WIDTH * LEFT / 4) feynman_diagram = self.get_feynman_diagram() feynman_diagram.next_to(ORIGIN, RIGHT) fd_parts = VGroup(*reversed(feynman_diagram.family_members_with_points())) # As a physicist self.play(self.get_book_intro(books[0])) self.play(LaggedStartMap( Write, feynman_diagram, run_time=4 )) self.wait() self.play( self.get_book_intro(books[1]), self.get_book_outro(books[0]), LaggedStartMap( ApplyMethod, fd_parts, lambda m: (m.scale, 0), run_time=1 ), ) self.remove(feynman_diagram) self.wait() # As a public figure safe = SVGMobject(file_name="safe", height=2) safe_rect = SurroundingRectangle(safe, buff=0) safe_rect.set_stroke(width=0) safe_rect.set_fill(DARK_GREY, 1) safe.add_to_back(safe_rect) bongo = SVGMobject(file_name="bongo") bongo.set_height(1) bongo.set_color(WHITE) bongo.next_to(safe, RIGHT, LARGE_BUFF) objects = VGroup(safe, bongo) feynman_smile = ImageMobject("Feynman_Los_Alamos") feynman_smile.set_height(4) feynman_smile.next_to(objects, DOWN) VGroup(objects, feynman_smile).next_to(ORIGIN, RIGHT) joke = TextMobject( "``Science is the belief \\\\ in the ignorance of \\\\ experts.''" ) joke.move_to(objects) self.play(LaggedStartMap( DrawBorderThenFill, objects, lag_ratio=0.75 )) self.play(self.get_book_intro(feynman_smile)) self.wait() self.play( objects.shift, 2 * UP, VFadeOut(objects) ) self.play(Write(joke)) self.wait(2) self.play( self.get_book_intro(books[2]), self.get_book_outro(books[1]), LaggedStartMap(FadeOut, joke, run_time=1), ApplyMethod( feynman_smile.shift, FRAME_HEIGHT * DOWN, remover=True ) ) # As a teacher feynman_teacher = ImageMobject("Feynman_teaching") feynman_teacher.set_width(FRAME_WIDTH / 2 - 1) feynman_teacher.next_to(ORIGIN, RIGHT) self.play(self.get_book_intro(feynman_teacher)) self.wait(3) def get_book_animation(self, book, initial_shift, animated_shift, opacity_func ): rect = BackgroundRectangle(book, fill_opacity=1) book.shift(initial_shift) return AnimationGroup( ApplyMethod(book.shift, animated_shift), UpdateFromAlphaFunc( rect, lambda r, a: r.move_to(book).set_fill( opacity=opacity_func(a) ), remover=True ) ) def get_book_intro(self, book): return self.get_book_animation( book, 2 * DOWN, 2 * UP, lambda a: 1 - a ) def get_book_outro(self, book): return ApplyMethod(book.shift, FRAME_HEIGHT * UP, remover=True) def get_feynman_diagram(self): x_min = -1.5 x_max = 1.5 arrow = Arrow(LEFT, RIGHT, buff=0) arrow.tip.move_to(arrow.get_center()) arrows = VGroup(*[ arrow.copy().rotate(angle).next_to(point, vect, buff=0) for (angle, point, vect) in [ (-45 * DEGREES, x_min * RIGHT, UL), (-135 * DEGREES, x_min * RIGHT, DL), (-135 * DEGREES, x_max * RIGHT, UR), (-45 * DEGREES, x_max * RIGHT, DR), ] ]) labels = VGroup(*[ TexMobject(tex) for tex in ["e^-", "e^+", "\\text{\\=q}", "q"] ]) vects = [UR, DR, UL, DL] for arrow, label, vect in zip(arrows, labels, vects): label.next_to(arrow.get_center(), vect, buff=SMALL_BUFF) wave = FunctionGraph( lambda x: 0.2 * np.sin(2 * TAU * x), x_min=x_min, x_max=x_max, ) wave_label = TexMobject("\\gamma") wave_label.next_to(wave, UP, SMALL_BUFF) labels.add(wave_label) squiggle = ParametricFunction( lambda t: np.array([ t + 0.5 * np.sin(TAU * t), 0.5 * np.cos(TAU * t), 0, ]), t_min=0, t_max=4, ) squiggle.scale(0.25) squiggle.set_color(BLUE) squiggle.rotate(-30 * DEGREES) squiggle.next_to( arrows[2].point_from_proportion(0.75), DR, buff=0 ) squiggle_label = TexMobject("g") squiggle_label.next_to(squiggle, UR, buff=-MED_SMALL_BUFF) labels.add(squiggle_label) return VGroup(arrows, wave, squiggle, labels) class FeynmanLecturesScreenCaptureFrame(Scene): def construct(self): url = TextMobject("http://www.feynmanlectures.caltech.edu/") url.to_edge(UP) screen_rect = ScreenRectangle(height=6) screen_rect.next_to(url, DOWN) self.add(url) self.play(ShowCreation(screen_rect)) self.wait() class TheMotionOfPlanets(Scene): CONFIG = { "camera_config": {"background_opacity": 1}, "random_seed": 2, } def construct(self): self.add_title() self.setup_orbits() def add_title(self): title = TextMobject("``The motion of planets around the sun''") title.set_color(YELLOW) title.to_edge(UP) title.add_to_back(title.copy().set_stroke(BLACK, 5)) self.add(title) self.title = title def setup_orbits(self): sun = ImageMobject("sun") sun.set_height(0.7) planets, ellipses, orbits = self.get_planets_ellipses_and_orbits(sun) archivist_words = TextMobject( "Judith Goodstein (Caltech archivist)" ) archivist_words.to_corner(UL) archivist_words.shift(1.5 * DOWN) archivist_words.add_background_rectangle() alt_name = TextMobject("David Goodstein (Caltech physicist)") alt_name.next_to(archivist_words, DOWN, aligned_edge=LEFT) alt_name.add_background_rectangle() book = ImageMobject("Lost_Lecture_cover") book.set_height(4) book.next_to(alt_name, DOWN) self.add(SunAnimation(sun)) self.add(ellipses, planets) self.add(self.title) self.add(*orbits) self.add_foreground_mobjects(planets) self.wait(10) self.play( VGroup(ellipses, sun).shift, 3 * RIGHT, FadeInFromDown(archivist_words), Animation(self.title) ) self.add_foreground_mobjects(archivist_words) self.wait(3) self.play(FadeInFromDown(alt_name)) self.add_foreground_mobjects(alt_name) self.wait() self.play(FadeInFromDown(book)) self.wait(15) def get_planets_ellipses_and_orbits(self, sun): planets = VGroup( ImageMobject("mercury"), ImageMobject("venus"), ImageMobject("earth"), ImageMobject("mars"), ImageMobject("comet") ) sizes = [0.383, 0.95, 1.0, 0.532, 0.3] orbit_radii = [0.254, 0.475, 0.656, 1.0, 3.0] orbit_eccentricies = [0.206, 0.006, 0.0167, 0.0934, 0.967] for planet, size in zip(planets, sizes): planet.set_height(0.5) planet.scale(size) ellipses = VGroup(*[ Circle(radius=r, color=WHITE, stroke_width=1) for r in orbit_radii ]) for circle, ec in zip(ellipses, orbit_eccentricies): a = circle.get_height() / 2 c = ec * a b = np.sqrt(a**2 - c**2) circle.stretch(b / a, 1) c = np.sqrt(a**2 - b**2) circle.shift(c * RIGHT) for circle in ellipses: circle.rotate( TAU * np.random.random(), about_point=ORIGIN ) ellipses.scale(3.5, about_point=ORIGIN) orbits = [ Orbiting( planet, sun, circle, rate=0.25 * r**(2 / 3) ) for planet, circle, r in zip(planets, ellipses, orbit_radii) ] orbits[-1].proportion = 0.15 orbits[-1].rate = 0.5 return planets, ellipses, orbits class TeacherHoldingUp(TeacherStudentsScene): def construct(self): self.play( self.teacher.change, "raise_right_hand" ) self.change_all_student_modes("pondering") self.look_at(ORIGIN) self.wait(5) class AskAboutEllipses(TheMotionOfPlanets): CONFIG = { "camera_config": {"background_opacity": 1}, "sun_height": 0.5, "sun_center": ORIGIN, "animate_sun": True, "a": 3.5, "b": 2.0, "ellipse_color": WHITE, "ellipse_stroke_width": 1, "comet_height": 0.2, } def construct(self): self.add_title() self.add_sun() self.add_orbit() self.add_focus_lines() self.add_force_labels() self.comment_on_imperfections() self.set_up_differential_equations() def add_title(self): title = Title("Why are orbits ellipses?") self.add(title) self.title = title def add_sun(self): sun = ImageMobject("sun", height=self.sun_height) sun.move_to(self.sun_center) self.sun = sun self.add(sun) if self.animate_sun: sun_animation = SunAnimation(sun) self.add(sun_animation) self.add_foreground_mobjects( sun_animation.mobject ) else: self.add_foreground_mobjects(sun) def add_orbit(self): sun = self.sun comet = self.get_comet() ellipse = self.get_ellipse() orbit = Orbiting(comet, sun, ellipse) self.add(ellipse) self.add(orbit) self.ellipse = ellipse self.comet = comet self.orbit = orbit def add_focus_lines(self): f1, f2 = self.focus_points comet = self.comet lines = VGroup(Line(LEFT, RIGHT), Line(LEFT, RIGHT)) lines.set_stroke(LIGHT_GREY, 1) def update_lines(lines): l1, l2 = lines P = comet.get_center() l1.put_start_and_end_on(f1, P) l2.put_start_and_end_on(f2, P) return lines animation = Mobject.add_updater( lines, update_lines ) self.add(animation) self.wait(8) self.focus_lines = lines self.focus_lines_animation = animation def add_force_labels(self): radial_line = self.focus_lines[0] # Radial line measurement radius_measurement_kwargs = { "num_decimal_places": 3, "color": BLUE, } radius_measurement = DecimalNumber(1, **radius_measurement_kwargs) def update_radial_measurement(measurement): angle = -radial_line.get_angle() + np.pi radial_line.rotate(angle, about_point=ORIGIN) new_decimal = DecimalNumber( radial_line.get_length(), **radius_measurement_kwargs ) max_width = 0.6 * radial_line.get_width() if new_decimal.get_width() > max_width: new_decimal.set_width(max_width) new_decimal.next_to(radial_line, UP, SMALL_BUFF) VGroup(new_decimal, radial_line).rotate( -angle, about_point=ORIGIN ) Transform(measurement, new_decimal).update(1) radius_measurement_animation = Mobject.add_updater( radius_measurement, update_radial_measurement ) # Force equation force_equation = TexMobject( "F = {GMm \\over (0.000)^2}", tex_to_color_map={ "F": YELLOW, "0.000": BLACK, } ) force_equation.next_to(self.title, DOWN) force_equation.to_edge(RIGHT) radius_in_denominator_ref = force_equation.get_part_by_tex("0.000") radius_in_denominator = DecimalNumber( 0, **radius_measurement_kwargs ) radius_in_denominator.scale(0.95) update_radius_in_denominator = ContinualChangingDecimal( radius_in_denominator, lambda a: radial_line.get_length(), position_update_func=lambda mob: mob.move_to( radius_in_denominator_ref, LEFT ) ) # Force arrow force_arrow, force_arrow_animation = self.get_force_arrow_and_update( self.comet ) inverse_square_law_words = TextMobject( "``Inverse square law''" ) inverse_square_law_words.next_to(force_equation, DOWN, MED_LARGE_BUFF) inverse_square_law_words.to_edge(RIGHT) force_equation.next_to(inverse_square_law_words, UP, MED_LARGE_BUFF) def v_fade_in(mobject): return UpdateFromAlphaFunc( mobject, lambda mob, alpha: mob.set_fill(opacity=alpha) ) self.add(update_radius_in_denominator) self.add(radius_measurement_animation) self.play( FadeIn(force_equation), v_fade_in(radius_in_denominator), v_fade_in(radius_measurement) ) self.add(force_arrow_animation) self.play(v_fade_in(force_arrow)) self.wait(8) self.play(Write(inverse_square_law_words)) self.wait(9) self.force_equation = force_equation self.inverse_square_law_words = inverse_square_law_words self.force_arrow = force_arrow self.radius_measurement = radius_measurement def comment_on_imperfections(self): planets, ellipses, orbits = self.get_planets_ellipses_and_orbits(self.sun) orbits.pop(-1) ellipses.submobjects.pop(-1) planets.submobjects.pop(-1) scale_factor = 20 center = self.sun.get_center() ellipses.save_state() ellipses.scale(scale_factor, about_point=center) self.add(*orbits) self.play(ellipses.restore, Animation(planets)) self.wait(7) self.play( ellipses.scale, scale_factor, {"about_point": center}, Animation(planets) ) self.remove(*orbits) self.remove(planets, ellipses) self.wait(2) def set_up_differential_equations(self): d_dt = TexMobject("{d \\over dt}") in_vect = Matrix(np.array([ "x(t)", "y(t)", "\\dot{x}(t)", "\\dot{y}(t)", ])) equals = TexMobject("=") out_vect = Matrix(np.array([ "\\dot{x}(t)", "\\dot{y}(t)", "-x(t) / (x(t)^2 + y(t)^2)^{3/2}", "-y(t) / (x(t)^2 + y(t)^2)^{3/2}", ]), element_alignment_corner=ORIGIN) equation = VGroup(d_dt, in_vect, equals, out_vect) equation.arrange(RIGHT, buff=SMALL_BUFF) equation.set_width(6) equation.to_corner(DR, buff=MED_LARGE_BUFF) cross = Cross(equation) self.play(Write(equation)) self.wait(6) self.play(ShowCreation(cross)) self.wait(6) # Helpers def get_comet(self): comet = ImageMobject("comet") comet.set_height(self.comet_height) return comet def get_ellipse(self): a = self.a b = self.b c = np.sqrt(a**2 - b**2) ellipse = Circle(radius=a) ellipse.set_stroke( self.ellipse_color, self.ellipse_stroke_width, ) ellipse.stretch(fdiv(b, a), dim=1) ellipse.move_to( self.sun.get_center() + c * LEFT, ) self.focus_points = [ self.sun.get_center(), self.sun.get_center() + 2 * c * LEFT, ] return ellipse def get_force_arrow_and_update(self, comet, scale_factor=1): force_arrow = Arrow(LEFT, RIGHT, color=YELLOW) sun = self.sun def update_force_arrow(arrow): radial_line = Line( sun.get_center(), comet.get_center() ) radius = radial_line.get_length() # target_length = 1 / radius**2 target_length = scale_factor / radius # Lies! arrow.scale( target_length / arrow.get_length() ) arrow.rotate( np.pi + radial_line.get_angle() - arrow.get_angle() ) arrow.shift( radial_line.get_end() - arrow.get_start() ) force_arrow_animation = Mobject.add_updater( force_arrow, update_force_arrow ) return force_arrow, force_arrow_animation def get_radial_line_and_update(self, comet): line = Line(LEFT, RIGHT) line.set_stroke(LIGHT_GREY, 1) line_update = Mobject.add_updater( line, lambda l: l.put_start_and_end_on( self.sun.get_center(), comet.get_center(), ) ) return line, line_update class FeynmanSaysItBest(TeacherStudentsScene): def construct(self): self.teacher_says( "Feynman says \\\\ it best", added_anims=[ self.get_student_changes( "hooray", "happy", "erm" ) ] ) self.wait(3) class FeynmanElementaryQuote(Scene): def construct(self): quote_text = """ \\large I am going to give what I will call an \\emph{elementary} demonstration. But elementary does not mean easy to understand. Elementary means that very little is required to know ahead of time in order to understand it, except to have an infinite amount of intelligence. """ quote_parts = [s for s in quote_text.split(" ") if s] quote = TextMobject( *quote_parts, tex_to_color_map={ "\\emph{elementary}": BLUE, "elementary": BLUE, "Elementary": BLUE, "infinite": YELLOW, "amount": YELLOW, "of": YELLOW, "intelligence": YELLOW, "very": RED, "little": RED, }, alignment="" ) quote[-1].shift(2 * SMALL_BUFF * LEFT) quote.set_width(FRAME_WIDTH - 1) quote.to_edge(UP) quote.get_part_by_tex("of").set_color(WHITE) nothing = TextMobject("nothing") nothing.scale(0.9) very = quote.get_part_by_tex("very") nothing.shift(very[0].get_left() - nothing[0].get_left()) nothing.set_color(RED) for word in quote: if word is very: self.add_foreground_mobjects(nothing) self.play(ShowWord(nothing)) self.wait(0.2) nothing.sort(lambda p: -p[0]) self.play(LaggedStartMap( FadeOut, nothing, run_time=1 )) self.remove_foreground_mobject(nothing) back_word = word.copy().set_stroke(BLACK, 5) self.add_foreground_mobjects(back_word, word) self.play( ShowWord(back_word), ShowWord(word), ) self.wait(0.005 * len(word)**1.5) self.wait() # Show thumbnails images = Group( ImageMobject("Calculus_Thumbnail"), ImageMobject("Fourier_Thumbnail"), ) for image in images: image.set_height(3) images.arrange(RIGHT, buff=LARGE_BUFF) images.to_edge(DOWN, buff=LARGE_BUFF) images[1].move_to(images[0]) crosses = VGroup(*list(map(Cross, images))) crosses.set_stroke("RED", 10) for image, cross in zip(images, crosses): image.rect = SurroundingRectangle( image, stroke_width=3, stroke_color=WHITE, buff=0 ) cross.scale(1.1) self.play( FadeInFromDown(images[0]), FadeInFromDown(images[0].rect) ) self.play(ShowCreation(crosses[0])) self.wait() self.play( FadeOutAndShiftDown(images[0]), FadeOutAndShiftDown(images[0].rect), FadeOutAndShiftDown(crosses[0]), FadeInFromDown(images[1]), FadeInFromDown(images[1].rect), ) self.play(ShowCreation(crosses[1])) self.wait() class LostLecturePicture(TODOStub): CONFIG = {"camera_config": {"background_opacity": 1}} def construct(self): picture = ImageMobject("Feynman_teaching") picture.set_height(FRAME_WIDTH) picture.to_corner(UL, buff=0) picture.fade(0.5) self.play( picture.to_corner, DR, {"buff": 0}, picture.shift, 1.5 * DOWN, path_arc=60 * DEGREES, run_time=20, rate_func=bezier([0, 0, 1, 1]) ) class AskAboutInfiniteIntelligence(TeacherStudentsScene): def construct(self): self.student_says( "Infinite intelligence?", target_mode="confused" ) self.play( self.get_student_changes("horrified", "confused", "sad"), self.teacher.change, "happy", ) self.wait() self.teacher_says( "Stay focused, \\\\ go full screen, \\\\ and you'll be fine.", added_anims=[self.get_student_changes(*["happy"] * 3)] ) self.wait() self.look_at(self.screen) self.wait(5) class TableOfContents(Scene): def construct(self): items = VGroup( TextMobject("How the ellipse will arise"), TextMobject("Kepler's 2nd law"), TextMobject("The shape of velocities"), ) items.arrange( DOWN, buff=LARGE_BUFF, aligned_edge=LEFT ) items.to_edge(LEFT, buff=1.5) for item in items: item.add(Dot().next_to(item, LEFT)) item.generate_target() item.target.set_fill(GREY, opacity=0.5) title = Title("The plan") scale_factor = 1.2 self.add(title) self.play(LaggedStartMap( FadeIn, items, run_time=1, lag_ratio=0.7, )) self.wait() for item in items: other_items = VGroup(*[m for m in items if m is not item]) new_item = item.copy() new_item.scale(scale_factor, about_edge=LEFT) new_item.set_fill(WHITE, 1) self.play( Transform(item, new_item), *list(map(MoveToTarget, other_items)) ) self.wait() class DrawEllipseOverlay(Scene): def construct(self): ellipse = Circle() ellipse.stretch_to_fit_width(7.0) ellipse.stretch_to_fit_height(3.8) ellipse.shift(1.05 * UP + 0.48 * LEFT) ellipse.set_stroke(RED, 8) image = ImageMobject( os.path.join( get_image_output_directory(self.__class__), "HeldUpEllipse.jpg" ) ) image.set_height(FRAME_HEIGHT) # self.add(image) self.play(ShowCreation(ellipse)) self.wait() self.play(FadeOut(ellipse)) class ShowEllipseDefiningProperty(Scene): CONFIG = { "camera_config": {"background_opacity": 1}, "ellipse_color": BLUE, "a": 4.0, "b": 3.0, "distance_labels_scale_factor": 1.0, } def construct(self): self.add_ellipse() self.add_focal_lines() self.add_distance_labels() self.label_foci() self.label_focal_sum() def add_ellipse(self): a = self.a b = self.b ellipse = Circle(radius=a, color=self.ellipse_color) ellipse.stretch(fdiv(b, a), dim=1) ellipse.to_edge(LEFT, buff=LARGE_BUFF) self.ellipse = ellipse self.add(ellipse) def add_focal_lines(self): push_pins = VGroup(*[ SVGMobject( file_name="push_pin", color=LIGHT_GREY, fill_opacity=0.8, height=0.5, ).move_to(point, DR).shift(0.05 * RIGHT) for point in self.get_foci() ]) dot = Dot() dot.scale(0.5) position_tracker = ValueTracker(0.125) dot_update = Mobject.add_updater( dot, lambda d: d.move_to( self.ellipse.point_from_proportion( position_tracker.get_value() % 1 ) ) ) always_shift(position_tracker, rate=0.05) lines, lines_update_animation = self.get_focal_lines_and_update( self.get_foci, dot ) self.add_foreground_mobjects(push_pins, dot) self.add(dot_update) self.play(LaggedStartMap( FadeInFrom, push_pins, lambda m: (m, 2 * UP + LEFT), run_time=1, lag_ratio=0.75 )) self.play(ShowCreation(lines)) self.add(lines_update_animation) self.add(position_tracker) self.wait(2) self.position_tracker = position_tracker self.focal_lines = lines def add_distance_labels(self): lines = self.focal_lines colors = [YELLOW, PINK] distance_labels, distance_labels_animation = \ self.get_distance_labels_and_update(lines, colors) sum_expression, numbers, number_updates = \ self.get_sum_expression_and_update( lines, colors, lambda mob: mob.to_corner(UR) ) sum_expression_fading_rect = BackgroundRectangle( sum_expression, fill_opacity=1 ) sum_rect = SurroundingRectangle(numbers[-1]) constant_words = TextMobject("Stays constant") constant_words.next_to(sum_rect, DOWN, aligned_edge=RIGHT) VGroup(sum_rect, constant_words).set_color(BLUE) self.add(distance_labels_animation) self.add(*number_updates) self.add(sum_expression) self.add_foreground_mobjects(sum_expression_fading_rect) self.play( VFadeIn(distance_labels), FadeOut(sum_expression_fading_rect), ) self.remove_foreground_mobject(sum_expression_fading_rect) self.wait(7) self.play( ShowCreation(sum_rect), Write(constant_words) ) self.wait(7) self.play(FadeOut(sum_rect), FadeOut(constant_words)) self.sum_expression = sum_expression self.sum_rect = sum_rect def label_foci(self): foci = self.get_foci() focus_words = VGroup(*[ TextMobject("Focus").next_to(focus, DOWN) for focus in foci ]) foci_word = TextMobject("Foci") foci_word.move_to(focus_words) foci_word.shift(MED_SMALL_BUFF * UP) connecting_lines = VGroup(*[ Arrow( foci_word.get_edge_center(-edge), focus_word.get_edge_center(edge), buff=MED_SMALL_BUFF, stroke_width=2, ) for focus_word, edge in zip(focus_words, [LEFT, RIGHT]) ]) translation = TextMobject( "``Foco'' $\\rightarrow$ Fireplace" ) translation.to_edge(RIGHT) translation.shift(UP) sun = ImageMobject("sun", height=0.5) sun.move_to(foci[0]) sun_animation = SunAnimation(sun) self.play(FadeInFromDown(focus_words)) self.wait(2) self.play( ReplacementTransform(focus_words.copy(), foci_word), ) self.play(*list(map(ShowCreation, connecting_lines))) for word in list(focus_words) + [foci_word]: word.add_background_rectangle() self.add_foreground_mobjects(word) self.wait(4) self.play(Write(translation)) self.wait(2) self.play(GrowFromCenter(sun)) self.add(sun_animation) self.wait(8) def label_focal_sum(self): sum_rect = self.sum_rect focal_sum = TextMobject("``Focal sum''") focal_sum.scale(1.5) focal_sum.next_to(sum_rect, DOWN, aligned_edge=RIGHT) VGroup(sum_rect, focal_sum).set_color(RED) footnote = TextMobject( """ \\Large *This happens to equal the longest distance across the ellipse, so perhaps the more standard terminology would be ``major axis'', but I want some terminology that conveys the idea of adding two distances to the foci. """, alignment="", ) footnote.set_width(5) footnote.to_corner(DR) footnote.set_stroke(WHITE, 0.5) self.play(FadeInFromDown(focal_sum)) self.play(Write(sum_rect)) self.wait() self.play(FadeIn(footnote)) self.wait(2) self.play(FadeOut(footnote)) self.wait(8) # Helpers def get_foci(self): ellipse = self.ellipse a = ellipse.get_width() / 2 b = ellipse.get_height() / 2 c = np.sqrt(a**2 - b**2) center = ellipse.get_center() return [ center + c * RIGHT, center + c * LEFT, ] def get_focal_lines_and_update(self, get_foci, focal_sum_point): lines = VGroup(Line(LEFT, RIGHT), Line(LEFT, RIGHT)) lines.set_stroke(width=2) def update_lines(lines): foci = get_foci() for line, focus in zip(lines, foci): line.put_start_and_end_on( focus, focal_sum_point.get_center() ) lines[1].rotate(np.pi) lines_update_animation = Mobject.add_updater( lines, update_lines ) return lines, lines_update_animation def get_distance_labels_and_update(self, lines, colors): distance_labels = VGroup( DecimalNumber(0), DecimalNumber(0), ) for label in distance_labels: label.scale(self.distance_labels_scale_factor) def update_distance_labels(labels): for label, line, color in zip(labels, lines, colors): angle = -line.get_angle() if np.abs(angle) > 90 * DEGREES: angle = 180 * DEGREES + angle line.rotate(angle, about_point=ORIGIN) new_decimal = DecimalNumber(line.get_length()) new_decimal.scale( self.distance_labels_scale_factor ) max_width = 0.6 * line.get_width() if new_decimal.get_width() > max_width: new_decimal.set_width(max_width) new_decimal.next_to(line, UP, SMALL_BUFF) new_decimal.set_color(color) new_decimal.add_to_back( new_decimal.copy().set_stroke(BLACK, 5) ) VGroup(new_decimal, line).rotate( -angle, about_point=ORIGIN ) label.submobjects = list(new_decimal.submobjects) distance_labels_animation = Mobject.add_updater( distance_labels, update_distance_labels ) return distance_labels, distance_labels_animation def get_sum_expression_and_update(self, lines, colors, sum_position_func): sum_expression = TexMobject("0.00", "+", "0.00", "=", "0.00") sum_position_func(sum_expression) number_refs = sum_expression.get_parts_by_tex("0.00") number_refs.set_fill(opacity=0) numbers = VGroup(*[DecimalNumber(0) for ref in number_refs]) for number, color in zip(numbers, colors): number.set_color(color) # Not the most elegant... number_updates = [ ContinualChangingDecimal( numbers[0], lambda a: lines[0].get_length(), position_update_func=lambda m: m.move_to( number_refs[1], LEFT ) ), ContinualChangingDecimal( numbers[1], lambda a: lines[1].get_length(), position_update_func=lambda m: m.move_to( number_refs[0], LEFT ) ), ContinualChangingDecimal( numbers[2], lambda a: sum(map(Line.get_length, lines)), position_update_func=lambda m: m.move_to( number_refs[2], LEFT ) ), ] return sum_expression, numbers, number_updates class GeometryProofLand(Scene): CONFIG = { "colors": [ PINK, RED, YELLOW, GREEN, GREEN_A, BLUE, MAROON_E, MAROON_B, YELLOW, BLUE, ], "text": "Geometry proof land", } def construct(self): word = self.get_geometry_proof_land_word() word_outlines = word.deepcopy() word_outlines.set_fill(opacity=0) word_outlines.set_stroke(WHITE, 1) colors = list(self.colors) random.shuffle(colors) word_outlines.set_color_by_gradient(*colors) word_outlines.set_stroke(width=5) circles = VGroup() for letter in word: circle = Circle() # circle = letter.copy() circle.replace(letter, dim_to_match=1) circle.scale(3) circle.set_stroke(WHITE, 0) circle.set_fill(letter.get_color(), 0) circles.add(circle) circle.target = letter self.play( LaggedStartMap(MoveToTarget, circles), run_time=2 ) self.add(word_outlines, circles) self.play(LaggedStartMap( FadeIn, word_outlines, run_time=3, rate_func=there_and_back, ), Animation(circles)) self.wait() def get_geometry_proof_land_word(self): word = TextMobject(self.text) word.rotate(-90 * DEGREES) word.scale(0.25) word.shift(3 * RIGHT) word.apply_complex_function(np.exp) word.rotate(90 * DEGREES) word.set_width(9) word.center() word.to_edge(UP) word.set_color_by_gradient(*self.colors) word.set_background_stroke(width=0) return word class ProveEllipse(ShowEmergingEllipse, ShowEllipseDefiningProperty): CONFIG = { "eccentricity_vector": 1.5 * RIGHT, "ellipse_color": PINK, "distance_labels_scale_factor": 0.7, } def construct(self): self.setup_ellipse() self.hypothesize_foci() self.setup_and_show_focal_sum() self.show_circle_radius() self.limit_to_just_one_line() self.look_at_perpendicular_bisector() self.show_orbiting_planet() def setup_ellipse(self): circle = self.circle = self.get_circle() circle.to_edge(LEFT) ep = self.get_eccentricity_point() ep_dot = self.ep_dot = Dot(ep, color=YELLOW) lines = self.lines = self.get_lines() for line in lines: line.save_state() ghost_lines = self.ghost_lines = self.get_ghost_lines(lines) ellipse = self.ellipse = self.get_ellipse() self.add(ghost_lines, circle, lines, ep_dot) self.play( LaggedStartMap(MoveToTarget, lines), Animation(ep_dot), ) self.play(ShowCreation(ellipse)) self.wait() def hypothesize_foci(self): circle = self.circle ghost_lines = self.ghost_lines ghost_lines_copy = ghost_lines.copy().set_stroke(YELLOW, 3) center = circle.get_center() center_dot = Dot(center, color=RED) # ep = self.get_eccentricity_point() ep_dot = self.ep_dot dots = VGroup(center_dot, ep_dot) center_label = TextMobject("Circle center") ep_label = TextMobject("Eccentric point") labels = VGroup(center_label, ep_label) vects = [UL, DR] arrows = VGroup() for label, dot, vect in zip(labels, dots, vects): label.next_to(dot, vect, MED_LARGE_BUFF) label.match_color(dot) label.add_to_back( label.copy().set_stroke(BLACK, 5) ) arrow = Arrow( label.get_corner(-vect), dot.get_corner(vect), buff=SMALL_BUFF ) arrow.match_color(dot) arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) arrows.add(arrow) labels_target = labels.copy() labels_target.arrange( DOWN, aligned_edge=LEFT ) guess_start = TextMobject("Guess: Foci = ") brace = Brace(labels_target, LEFT) full_guess = VGroup(guess_start, brace, labels_target) full_guess.arrange(RIGHT) full_guess.to_corner(UR) self.play( FadeInFromDown(labels[1]), GrowArrow(arrows[1]), ) self.play(LaggedStartMap( ShowPassingFlash, ghost_lines_copy )) self.wait() self.play(ReplacementTransform(circle.copy(), center_dot)) self.add_foreground_mobjects(dots) self.play( FadeInFromDown(labels[0]), GrowArrow(arrows[0]), ) self.wait() self.play( Write(guess_start), GrowFromCenter(brace), run_time=1 ) self.play( ReplacementTransform(labels.copy(), labels_target) ) self.wait() self.play(FadeOut(labels), FadeOut(arrows)) self.center_dot = center_dot def setup_and_show_focal_sum(self): circle = self.circle ellipse = self.ellipse focal_sum_point = VectorizedPoint() focal_sum_point.move_to(circle.get_top()) dots = [self.ep_dot, self.center_dot] colors = list(map(Mobject.get_color, dots)) def get_foci(): return list(map(Mobject.get_center, dots)) focal_lines, focal_lines_update_animation = \ self.get_focal_lines_and_update(get_foci, focal_sum_point) distance_labels, distance_labels_update_animation = \ self.get_distance_labels_and_update(focal_lines, colors) sum_expression, numbers, number_updates = \ self.get_sum_expression_and_update( focal_lines, colors, lambda mob: mob.to_edge(RIGHT).shift(UP) ) to_add = self.focal_sum_things_to_add = [ focal_lines_update_animation, distance_labels_update_animation, sum_expression, ] + list(number_updates) self.play( ShowCreation(focal_lines), Write(distance_labels), FadeIn(sum_expression), Write(numbers), run_time=1 ) self.wait() self.add(*to_add) points = [ ellipse.get_top(), circle.point_from_proportion(0.2), ellipse.point_from_proportion(0.2), ellipse.point_from_proportion(0.4), ] for point in points: self.play( focal_sum_point.move_to, point ) self.wait() self.remove(*to_add) self.play(*list(map(FadeOut, [ focal_lines, distance_labels, sum_expression, numbers ]))) self.set_variables_as_attrs( focal_lines, focal_lines_update_animation, focal_sum_point, distance_labels, distance_labels_update_animation, sum_expression, numbers, number_updates ) def show_circle_radius(self): circle = self.circle center = circle.get_center() point = circle.get_right() color = GREEN radius = Line(center, point, color=color) radius_measurement = DecimalNumber(radius.get_length()) radius_measurement.set_color(color) radius_measurement.next_to(radius, UP, SMALL_BUFF) radius_measurement.add_to_back( radius_measurement.copy().set_stroke(BLACK, 5) ) group = VGroup(radius, radius_measurement) group.rotate(30 * DEGREES, about_point=center) self.play(ShowCreation(radius)) self.play(Write(radius_measurement)) self.wait() self.play(Rotating( group, rate_func=smooth, run_time=7, about_point=center )) self.play(FadeOut(group)) def limit_to_just_one_line(self): lines = self.lines ghost_lines = self.ghost_lines ep_dot = self.ep_dot index = int(0.2 * len(lines)) line = lines[index] ghost_line = ghost_lines[index] to_fade = VGroup(*list(lines) + list(ghost_lines)) to_fade.remove(line, ghost_line) P_dot = Dot(line.saved_state.get_end()) P_label = TexMobject("P") P_label.next_to(P_dot, UP, SMALL_BUFF) self.add_foreground_mobjects(self.ellipse) self.play(LaggedStartMap(Restore, lines)) self.play( FadeOut(to_fade), ghost_line.set_stroke, YELLOW, 3, line.set_stroke, WHITE, 3, ReplacementTransform(ep_dot.copy(), P_dot), ) self.play(FadeInFromDown(P_label)) self.wait() for l in lines: l.generate_target() l.target.rotate( 90 * DEGREES, about_point=l.get_center() ) self.set_variables_as_attrs( line, ghost_line, P_dot, P_label ) def look_at_perpendicular_bisector(self): # Alright, this method's gonna blow up. Let's go! circle = self.circle ellipse = self.ellipse ellipse.save_state() lines = self.lines line = self.line ghost_lines = self.ghost_lines ghost_line = self.ghost_line P_dot = self.P_dot P_label = self.P_label elbow = self.get_elbow(line) self.play( MoveToTarget(line, path_arc=90 * DEGREES), ShowCreation(elbow) ) # Perpendicular bisector label label = TextMobject("``Perpendicular bisector''") label.scale(0.75) label.set_color(YELLOW) label.next_to(ORIGIN, UP, MED_SMALL_BUFF) label.add_background_rectangle() angle = line.get_angle() + np.pi label.rotate(angle, about_point=ORIGIN) label.shift(line.get_center()) # Dot defining Q point Q_dot = Dot(color=GREEN) Q_dot.move_to(self.focal_sum_point) focal_sum_point_animation = turn_animation_into_updater( MaintainPositionRelativeTo( self.focal_sum_point, Q_dot ) ) self.add(focal_sum_point_animation) Q_dot.move_to(line.point_from_proportion(0.9)) Q_dot.save_state() Q_label = TexMobject("Q") Q_label.scale(0.7) Q_label.match_color(Q_dot) Q_label.add_to_back(Q_label.copy().set_stroke(BLACK, 5)) Q_label.next_to(Q_dot, UL, buff=0) Q_label_animation = turn_animation_into_updater( MaintainPositionRelativeTo(Q_label, Q_dot) ) # Pretty hacky... def distance_label_shift_update(label): line = self.focal_lines[0] if line.get_end()[0] > line.get_start()[0]: vect = label.get_center() - line.get_center() label.shift(-2 * vect) distance_label_shift_update_animation = Mobject.add_updater( self.distance_labels[0], distance_label_shift_update ) self.focal_sum_things_to_add.append( distance_label_shift_update_animation ) # Define QP line QP_line = Line(LEFT, RIGHT) QP_line.match_style(self.focal_lines) QP_line_update = Mobject.add_updater( QP_line, lambda l: l.put_start_and_end_on( Q_dot.get_center(), P_dot.get_center(), ) ) QE_line = Line(LEFT, RIGHT) QE_line.set_stroke(YELLOW, 3) QE_line_update = Mobject.add_updater( QE_line, lambda l: l.put_start_and_end_on( Q_dot.get_center(), self.get_eccentricity_point() ) ) # Define similar triangles triangles = VGroup(*[ Polygon( Q_dot.get_center(), line.get_center(), end_point, fill_opacity=1, ) for end_point in [ P_dot.get_center(), self.get_eccentricity_point() ] ]) triangles.set_color_by_gradient(RED_C, COBALT) triangles.set_stroke(WHITE, 2) # Add even more distant label updates def distance_label_rotate_update(label): QE_line_update.update(0) angle = QP_line.get_angle() - QE_line.get_angle() label.rotate(angle, about_point=Q_dot.get_center()) return label distance_label_rotate_update_animation = Mobject.add_updater( self.distance_labels[0], distance_label_rotate_update ) # Hook up line to P to P_dot radial_line = DashedLine(ORIGIN, 3 * RIGHT) radial_line_update = UpdateFromFunc( radial_line, lambda l: l.put_start_and_end_on( circle.get_center(), P_dot.get_center() ) ) def put_dot_at_intersection(dot): point = line_intersection( line.get_start_and_end(), radial_line.get_start_and_end() ) dot.move_to(point) return dot keep_Q_dot_at_intersection = UpdateFromFunc( Q_dot, put_dot_at_intersection ) Q_dot.restore() ghost_line_update_animation = UpdateFromFunc( ghost_line, lambda l: l.put_start_and_end_on( self.get_eccentricity_point(), P_dot.get_center() ) ) def update_perp_bisector(line): line.scale(ghost_line.get_length() / line.get_length()) line.rotate(ghost_line.get_angle() - line.get_angle()) line.rotate(90 * DEGREES) line.move_to(ghost_line) perp_bisector_update_animation = UpdateFromFunc( line, update_perp_bisector ) elbow_update_animation = UpdateFromFunc( elbow, lambda e: Transform(e, self.get_elbow(ghost_line)).update(1) ) P_dot_movement_updates = [ radial_line_update, keep_Q_dot_at_intersection, MaintainPositionRelativeTo( P_label, P_dot ), ghost_line_update_animation, perp_bisector_update_animation, elbow_update_animation, ] # Comment for tangency sum_rect = SurroundingRectangle( self.numbers[-1] ) tangency_comment = TextMobject( "Always $\\ge$ radius" ) tangency_comment.next_to( sum_rect, DOWN, aligned_edge=RIGHT ) VGroup(sum_rect, tangency_comment).set_color(GREEN) # Why is this needed?!? self.add(*self.focal_sum_things_to_add) self.wait(0.01) self.remove(*self.focal_sum_things_to_add) # Show label self.play(Write(label)) self.wait() # Show Q_dot moving about a little self.play( FadeOut(label), FadeIn(self.focal_lines), FadeIn(self.distance_labels), FadeIn(self.sum_expression), FadeIn(self.numbers), ellipse.set_stroke, {"width": 0.5}, ) self.add(*self.focal_sum_things_to_add) self.play( FadeInFromDown(Q_label), GrowFromCenter(Q_dot) ) self.wait() self.add_foreground_mobjects(Q_dot) self.add(Q_label_animation) self.play( Q_dot.move_to, line.point_from_proportion(0.05), rate_func=there_and_back, run_time=4 ) self.wait() # Show similar triangles self.play( FadeIn(triangles[0]), ShowCreation(QP_line), Animation(elbow), ) self.add(QP_line_update) for i in range(3): self.play( FadeIn(triangles[(i + 1) % 2]), FadeOut(triangles[i % 2]), Animation(self.distance_labels), Animation(elbow) ) self.play( FadeOut(triangles[1]), Animation(self.distance_labels) ) # Move first distance label # (boy, this got messy...hopefully no one ever has # to read this.) angle = QP_line.get_angle() - QE_line.get_angle() Q_point = Q_dot.get_center() for x in range(2): self.play(ShowCreationThenDestruction(QE_line)) distance_label_copy = self.distance_labels[0].copy() self.play( ApplyFunction( distance_label_rotate_update, distance_label_copy, path_arc=angle ), Rotate(QE_line, angle, about_point=Q_point) ) self.play(FadeOut(QE_line)) self.remove(distance_label_copy) self.add(distance_label_rotate_update_animation) self.focal_sum_things_to_add.append( distance_label_rotate_update_animation ) self.wait() self.play( Q_dot.move_to, line.point_from_proportion(0), run_time=4, rate_func=there_and_back ) # Trace out ellipse self.play(ShowCreation(radial_line)) self.wait() self.play( ApplyFunction(put_dot_at_intersection, Q_dot), run_time=3, ) self.wait() self.play( Rotating( P_dot, about_point=circle.get_center(), rate_func=bezier([0, 0, 1, 1]), run_time=10, ), ellipse.restore, *P_dot_movement_updates ) self.wait() # Talk through tangency self.play( ShowCreation(sum_rect), Write(tangency_comment), ) points = [line.get_end(), line.get_start(), Q_dot.get_center()] run_times = [1, 3, 2] for point, run_time in zip(points, run_times): self.play(Q_dot.move_to, point, run_time=run_time) self.wait() self.remove(*self.focal_sum_things_to_add) self.play(*list(map(FadeOut, [ radial_line, QP_line, P_dot, P_label, Q_dot, Q_label, elbow, self.distance_labels, self.numbers, self.sum_expression, sum_rect, tangency_comment, ]))) self.wait() # Show all lines lines.remove(line) ghost_lines.remove(ghost_line) for line in lines: line.generate_target() line.target.rotate(90 * DEGREES) self.play( LaggedStartMap(FadeIn, ghost_lines), LaggedStartMap(FadeIn, lines), ) self.play(LaggedStartMap(MoveToTarget, lines)) self.wait() def show_orbiting_planet(self): ellipse = self.ellipse ep_dot = self.ep_dot planet = ImageMobject("earth") planet.set_height(0.25) orbit = Orbiting(planet, ep_dot, ellipse) lines = self.lines def update_lines(lines): for gl, line in zip(self.ghost_lines, lines): intersection = line_intersection( [self.circle.get_center(), gl.get_end()], line.get_start_and_end() ) distance = get_norm( intersection - planet.get_center() ) if distance < 0.025: line.set_stroke(BLUE, 3) self.add(line) else: line.set_stroke(WHITE, 1) lines_update_animation = Mobject.add_updater( lines, update_lines ) self.add(orbit) self.add(lines_update_animation) self.add_foreground_mobjects(planet) self.wait(12) class Enthusiast(Scene): def construct(self): randy = Randolph(color=BLUE_C) randy.flip() self.play(randy.change, "surprised") self.play(Blink(randy)) self.wait() class SimpleThinking(Scene): def construct(self): randy = Randolph(color=BLUE_C) randy.flip() self.play(randy.change, "thinking", 3 * UP) self.play(Blink(randy)) self.wait() self.play(randy.change, "hooray", 3 * UP) self.play(Blink(randy)) self.wait() class EndOfGeometryProofiness(GeometryProofLand): def construct(self): geometry_word = self.get_geometry_proof_land_word() orbital_mechanics = TextMobject("Orbital Mechanics") orbital_mechanics.scale(1.5) orbital_mechanics.to_edge(UP) underline = Line(LEFT, RIGHT) underline.match_width(orbital_mechanics) underline.next_to(orbital_mechanics, DOWN, SMALL_BUFF) self.play(LaggedStartMap(FadeOutAndShiftDown, geometry_word)) self.play(FadeInFromDown(orbital_mechanics)) self.play(ShowCreation(underline)) self.wait() class KeplersSecondLaw(AskAboutEllipses): CONFIG = { "sun_center": 4 * RIGHT + 0.75 * DOWN, "animate_sun": True, "a": 5.0, "b": 3.0, "ellipse_stroke_width": 2, "area_color": COBALT, "area_opacity": 0.75, "arc_color": YELLOW, "arc_stroke_width": 3, "n_sample_sweeps": 5, "fade_sample_areas": True, } def construct(self): self.add_title() self.add_sun() self.add_orbit() self.add_foreground_mobjects(self.comet) self.show_several_sweeps() self.contrast_close_to_far() def add_title(self): title = TextMobject("Kepler's 2nd law:") title.scale(1.0) title.to_edge(UP) self.add(title) self.title = title subtitle = TextMobject( "Orbits sweep a constant area per unit time" ) subtitle.next_to(title, DOWN, buff=0.2) subtitle.set_color(BLUE) self.add(subtitle) def show_several_sweeps(self): shown_areas = VGroup() for x in range(self.n_sample_sweeps): self.wait() area = self.show_area_sweep() shown_areas.add(area) self.wait() if self.fade_sample_areas: self.play(FadeOut(shown_areas)) def contrast_close_to_far(self): orbit = self.orbit sun_point = self.sun.get_center() start_prop = 0.9 self.wait_until_proportion(start_prop) self.show_area_sweep() end_prop = orbit.proportion arc = self.get_arc(start_prop, end_prop) radius = Line(sun_point, arc.points[0]) radius.set_color(WHITE) radius_words = self.get_radius_words(radius, "Short") radius_words.next_to(radius.get_center(), LEFT, SMALL_BUFF) arc_words = TextMobject("Long arc") arc_words.rotate(90 * DEGREES) arc_words.scale(0.5) arc_words.next_to(RIGHT, RIGHT) arc_words.apply_complex_function(np.exp) arc_words.scale(0.8) arc_words.next_to( arc, RIGHT, buff=-SMALL_BUFF ) arc_words.shift(4 * SMALL_BUFF * DOWN) arc_words.match_color(arc) # Show stubby arc # self.remove(orbit) # self.add(self.comet) self.play( ShowCreation(radius), Write(radius_words), ) self.play( ShowCreation(arc), Write(arc_words) ) # Show narrow arc # (Code repetition...uck) start_prop = 0.475 self.wait_until_proportion(start_prop) self.show_area_sweep() end_prop = orbit.proportion short_arc = self.get_arc(start_prop, end_prop) long_radius = Line(sun_point, short_arc.points[0]) long_radius.set_color(WHITE) long_radius_words = self.get_radius_words(long_radius, "Long") short_arc_words = TextMobject("Short arc") short_arc_words.scale(0.5) short_arc_words.rotate(90 * DEGREES) short_arc_words.next_to(short_arc, LEFT, SMALL_BUFF) short_arc_words.match_color(short_arc) self.play( ShowCreation(long_radius), Write(long_radius_words), ) self.play( ShowCreation(short_arc), Write(short_arc_words) ) self.wait(15) # Helpers def show_area_sweep(self, time=1.0): orbit = self.orbit start_prop = orbit.proportion area = self.get_area(start_prop, start_prop) area_update = UpdateFromFunc( area, lambda a: Transform( a, self.get_area(start_prop, orbit.proportion) ).update(1) ) self.play(area_update, run_time=time) return area def get_area(self, prop1, prop2): """ Return a mobject illustrating the area swept out between a point prop1 of the way along the ellipse, and prop2 of the way. """ sun_point = self.sun.get_center() arc = self.get_arc(prop1, prop2) # Add lines from start result = VMobject() result.append_vectorized_mobject( Line(sun_point, arc.points[0]) ) result.append_vectorized_mobject(arc) result.append_vectorized_mobject( Line(arc.points[-1], sun_point) ) result.set_stroke(WHITE, width=0) result.set_fill( self.area_color, self.area_opacity, ) return result def get_arc(self, prop1, prop2): ellipse = self.get_ellipse() prop1 = prop1 % 1.0 prop2 = prop2 % 1.0 if prop2 > prop1: arc = VMobject().pointwise_become_partial( ellipse, prop1, prop2 ) elif prop1 > prop2: arc, arc_extension = [ VMobject().pointwise_become_partial( ellipse, p1, p2 ) for p1, p2 in [(prop1, 1.0), (0.0, prop2)] ] arc.append_vectorized_mobject(arc_extension) else: arc = VectorizedPoint( ellipse.point_from_proportion(prop1) ) arc.set_stroke( self.arc_color, self.arc_stroke_width, ) return arc def wait_until_proportion(self, prop): if self.skip_animations: self.orbit.proportion = prop else: while (self.orbit.proportion % 1) < prop: self.wait(self.frame_duration) def get_radius_words(self, radius, adjective): radius_words = TextMobject( "%s radius" % adjective, ) min_width = 0.8 * radius.get_length() if radius_words.get_width() > min_width: radius_words.set_width(min_width) radius_words.match_color(radius) radius_words.next_to(ORIGIN, UP, SMALL_BUFF) angle = radius.get_angle() angle = ((angle + PI) % TAU) - PI if np.abs(angle) > PI / 2: angle += PI radius_words.rotate(angle, about_point=ORIGIN) radius_words.shift(radius.get_center()) return radius_words class NonEllipticalKeplersLaw(KeplersSecondLaw): CONFIG = { "animate_sun": True, "sun_center": 2 * RIGHT, "n_sample_sweeps": 10, } def construct(self): self.add_sun() self.add_orbit() self.show_several_sweeps() def add_orbit(self): sun = self.sun comet = ImageMobject("comet", height=0.5) orbit_shape = self.get_ellipse() orbit = self.orbit = Orbiting(comet, sun, orbit_shape) self.add(orbit_shape) self.add(orbit) arrow, arrow_update = self.get_force_arrow_and_update( comet ) alt_arrow_update = Mobject.add_updater( arrow, lambda a: a.scale( 1.0 / a.get_length(), about_point=a.get_start() ) ) self.add(arrow_update, alt_arrow_update) self.add_foreground_mobjects(comet, arrow) self.ellipse = orbit_shape def get_ellipse(self): orbit_shape = ParametricFunction( lambda t: (1 + 0.2 * np.sin(5 * TAU * t)) * np.array([ np.cos(TAU * t), np.sin(TAU * t), 0 ]) ) orbit_shape.set_height(7) orbit_shape.stretch(1.5, 0) orbit_shape.shift(LEFT) orbit_shape.set_stroke(LIGHT_GREY, 1) return orbit_shape class AngularMomentumArgument(KeplersSecondLaw): CONFIG = { "animate_sun": False, "sun_center": 4 * RIGHT + DOWN, "comet_start_point": 4 * LEFT, "comet_end_point": 5 * LEFT + DOWN, "comet_height": 0.3, } def construct(self): self.add_sun() self.show_small_sweep() self.show_sweep_dimensions() self.show_conservation_of_angular_momentum() def show_small_sweep(self): sun_center = self.sun_center comet_start = self.comet_start_point comet_end = self.comet_end_point triangle = Polygon( sun_center, comet_start, comet_end, fill_opacity=1, fill_color=COBALT, stroke_width=0, ) triangle.save_state() alt_triangle = Polygon( sun_center, interpolate(comet_start, comet_end, 0.9), comet_end ) alt_triangle.match_style(triangle) comet = self.get_comet() comet.move_to(comet_start) velocity_vector = Arrow( comet_start, comet_end, color=WHITE, buff=0 ) velocity_vector_label = TexMobject("\\vec{\\textbf{v}}") velocity_vector_label.next_to( velocity_vector.get_center(), UL, buff=SMALL_BUFF ) small_time_label = TextMobject( "Small", "time", "$\\Delta t$", ) small_time_label.to_edge(UP) small = small_time_label.get_part_by_tex("Small") small_rect = SurroundingRectangle(small) self.add_foreground_mobjects(comet) self.play( ShowCreation( triangle, rate_func=lambda t: interpolate(1.0 / 3, 2.0 / 3, t) ), MaintainPositionRelativeTo( velocity_vector, comet ), MaintainPositionRelativeTo( velocity_vector_label, velocity_vector, ), ApplyMethod( comet.move_to, comet_end, rate_func=linear, ), run_time=2, ) self.play(Write(small_time_label), run_time=2) self.wait() self.play( Transform(triangle, alt_triangle), ShowCreation(small_rect), small.set_color, YELLOW, ) self.wait() self.play( Restore(triangle), FadeOut(small_rect), small.set_color, WHITE, ) self.wait() self.triangle = triangle self.comet = comet self.delta_t = small_time_label.get_part_by_tex( "$\\Delta t$" ) self.velocity_vector = velocity_vector self.small_time_label = small_time_label def show_sweep_dimensions(self): triangle = self.triangle # velocity_vector = self.velocity_vector delta_t = self.delta_t comet = self.comet triangle_points = triangle.get_anchors()[:3] top = triangle_points[1] area_label = TexMobject( "\\text{Area}", "=", "\\frac{1}{2}", "\\text{Base}", "\\times", "\\text{Height}", ) area_label.set_color_by_tex_to_color_map({ "Base": PINK, "Height": YELLOW, }) area_label.to_edge(UP) equals = area_label.get_part_by_tex("=") area_expression = TexMobject( "=", "\\frac{1}{2}", "R", "\\times", "\\vec{\\textbf{v}}_\\perp", "\\Delta t", ) area_expression.set_color_by_tex_to_color_map({ "R": PINK, "textbf{v}": YELLOW, }) area_expression.next_to(area_label, DOWN) area_expression.align_to(equals, LEFT) self.R_v_perp = VGroup(*area_expression[-4:-1]) self.R_v_perp_rect = SurroundingRectangle( self.R_v_perp, stroke_color=BLUE, fill_color=BLACK, fill_opacity=1, ) base = Line(triangle_points[2], triangle_points[0]) base.set_stroke(PINK, 3) base_point = line_intersection( base.get_start_and_end(), [top, top + DOWN] ) height = Line(top, base_point) height.set_stroke(YELLOW, 3) radius_label = TextMobject("Radius") radius_label.next_to(base, DOWN, SMALL_BUFF) radius_label.match_color(base) R_term = area_expression.get_part_by_tex("R") R_term.save_state() R_term.move_to(radius_label[0]) R_term.set_fill(opacity=0.5) v_perp = Arrow(*height.get_start_and_end(), buff=0) v_perp.set_color(YELLOW) v_perp.shift(comet.get_center() - v_perp.get_start()) v_perp_label = TexMobject( "\\vec{\\textbf{v}}_\\perp" ) v_perp_label.set_color(YELLOW) v_perp_label.next_to(v_perp, RIGHT, buff=SMALL_BUFF) v_perp_delta_t = VGroup(v_perp_label.copy(), delta_t.copy()) v_perp_delta_t.generate_target() v_perp_delta_t.target.arrange(RIGHT, buff=SMALL_BUFF) v_perp_delta_t.target.next_to(height, RIGHT, SMALL_BUFF) self.small_time_label.add(v_perp_delta_t[1]) self.play( FadeInFromDown(area_label), self.small_time_label.scale, 0.5, self.small_time_label.to_corner, UL, ) self.wait() self.play( ShowCreation(base), Write(radius_label), ) self.wait() self.play(ShowCreation(height)) self.wait() self.play( GrowArrow(v_perp), Write(v_perp_label, run_time=1), ) self.wait() self.play(MoveToTarget(v_perp_delta_t)) self.wait() self.play(*[ ReplacementTransform( area_label.get_part_by_tex(tex).copy(), area_expression.get_part_by_tex(tex), ) for tex in ("=", "\\frac{1}{2}", "\\times") ]) self.play(Restore(R_term)) self.play(ReplacementTransform( v_perp_delta_t.copy(), VGroup(*area_expression[-2:]) )) self.wait() def show_conservation_of_angular_momentum(self): R_v_perp = self.R_v_perp R_v_perp_rect = self.R_v_perp_rect sun_center = self.sun_center comet = self.comet comet.save_state() vector_field = VectorField( get_force_field_func((sun_center, -1)) ) vector_field.set_fill(opacity=0.8) vector_field.sort( lambda p: -get_norm(p - sun_center) ) stays_constant = TextMobject("Stays constant") stays_constant.next_to( R_v_perp_rect, DR, buff=MED_LARGE_BUFF ) stays_constant.match_color(R_v_perp_rect) stays_constant_arrow = Arrow( stays_constant.get_left(), R_v_perp_rect.get_bottom(), color=R_v_perp_rect.get_color() ) sun_dot = Dot(sun_center, fill_opacity=0.25) big_dot = Dot(fill_opacity=0, radius=FRAME_WIDTH) R_v_perp.save_state() R_v_perp.generate_target() R_v_perp.target.to_edge(LEFT, buff=MED_LARGE_BUFF) lp, rp = parens = TexMobject("()") lp.next_to(R_v_perp.target, LEFT) rp.next_to(R_v_perp.target, RIGHT) self.play(Transform( big_dot, sun_dot, run_time=1, remover=True )) self.wait() self.play( DrawBorderThenFill(R_v_perp_rect), Animation(R_v_perp), Write(stays_constant, run_time=1), GrowArrow(stays_constant_arrow), ) self.wait() foreground = VGroup(*self.get_mobjects()) self.play( LaggedStartMap(GrowArrow, vector_field), Animation(foreground) ) for x in range(3): self.play( LaggedStartMap( ApplyFunction, vector_field, lambda mob: (lambda m: m.scale(1.1).set_fill(opacity=1), mob), rate_func=there_and_back, run_time=1 ), Animation(foreground) ) self.play( FadeIn(parens), MoveToTarget(R_v_perp), ) self.play( comet.scale, 2, comet.next_to, parens, RIGHT, {"buff": SMALL_BUFF} ) self.wait() self.play( FadeOut(parens), R_v_perp.restore, comet.restore, ) self.wait(3) class KeplersSecondLawImage(KeplersSecondLaw): CONFIG = { "animate_sun": False, "n_sample_sweeps": 8, "fade_sample_areas": False, } def construct(self): self.add_sun() self.add_foreground_mobjects(self.sun) self.add_orbit() self.add_foreground_mobjects(self.comet) self.show_several_sweeps() class HistoryOfAngularMomentum(TeacherStudentsScene): CONFIG = { "camera_config": {"fill_opacity": 1} } def construct(self): am = VGroup(TextMobject("Angular momentum")) k2l = TextMobject("Kepler's 2nd law") arrow = Arrow(ORIGIN, RIGHT) group = VGroup(am, arrow, k2l) group.arrange(RIGHT) group.next_to(self.hold_up_spot, UL) k2l_image = ImageMobject("Kepler2ndLaw") k2l_image.match_width(k2l) k2l_image.next_to(k2l, UP) k2l.add(k2l_image) angular_momentum_formula = TexMobject( "R", "\\times", "m", "\\vec{\\textbf{v}}_\\perp", ) angular_momentum_formula.set_color_by_tex_to_color_map({ "R": PINK, "v": YELLOW, }) angular_momentum_formula.next_to(am, UP) am.add(angular_momentum_formula) self.play( self.teacher.change, "raise_right_hand", FadeInFromDown(group), self.get_student_changes(*3 * ["pondering"]) ) self.wait() self.play( am.next_to, arrow, RIGHT, {"index_of_submobject_to_align": 0}, k2l.next_to, arrow, LEFT, {"index_of_submobject_to_align": 0}, path_arc=90 * DEGREES, run_time=2 ) self.wait(3) class FeynmanRecountingNewton(Scene): CONFIG = { "camera_config": {"background_opacity": 1}, } def construct(self): feynman_teaching = ImageMobject("Feynman_teaching") feynman_teaching.set_width(FRAME_WIDTH) newton = ImageMobject("Newton") principia = ImageMobject("Principia_equal_area") images = [newton, principia] for image in images: image.set_height(5) newton.to_corner(UL) principia.next_to(newton, RIGHT) for image in images: image.rect = SurroundingRectangle( image, color=WHITE, buff=0, ) self.play(FadeInFromDown(feynman_teaching, run_time=2)) self.wait() self.play( FadeInFromDown(newton), FadeInFromDown(newton.rect), ) self.wait() self.play(*[ FadeInFrom( mob, direction=3 * LEFT ) for mob in (principia, principia.rect) ]) self.wait() class IntroduceShapeOfVelocities(AskAboutEllipses, MovingCameraScene): CONFIG = { "animate_sun": True, "sun_center": 2 * RIGHT, "a": 4.0, "b": 3.5, "num_vectors": 25, } def construct(self): self.setup_orbit() self.warp_orbit() self.reference_inverse_square_law() self.show_velocity_vectors() self.collect_velocity_vectors() def setup_orbit(self): self.add_sun() self.add_orbit() self.add_foreground_mobjects(self.comet) def warp_orbit(self): def func(z, c=3.5): return 1 * (np.exp((1.0 / c) * (z) + 1) - np.exp(1)) ellipse = self.ellipse ellipse.save_state() ellipse.generate_target() ellipse.target.stretch(0.7, 1) ellipse.target.apply_complex_function(func) ellipse.target.replace(ellipse, dim_to_match=1) self.wait(5) self.play(MoveToTarget(ellipse, run_time=2)) self.wait(5) def reference_inverse_square_law(self): ellipse = self.ellipse force_equation = TexMobject( "F", "=", "{G", "M", "m", "\\over", "R^2}" ) force_equation.move_to(ellipse) force_equation.set_color_by_tex("F", YELLOW) force_arrow, force_arrow_update = self.get_force_arrow_and_update( self.comet, scale_factor=3, ) radial_line, radial_line_update = self.get_radial_line_and_update( self.comet ) self.add(radial_line_update) self.add(force_arrow_update) self.play( Restore(ellipse), Write(force_equation), UpdateFromAlphaFunc( force_arrow, lambda m, a: m.set_fill(opacity=a) ), UpdateFromAlphaFunc( radial_line, lambda m, a: m.set_stroke(width=a) ), ) self.wait(10) def show_velocity_vectors(self): alphas = np.linspace(0, 1, self.num_vectors, endpoint=False) vectors = VGroup(*[ self.get_velocity_vector(alpha) for alpha in alphas ]) moving_vector, moving_vector_animation = self.get_velocity_vector_and_update() self.play(LaggedStartMap( ShowCreation, vectors, lag_ratio=0.2, run_time=3, )) self.wait(5) self.add(moving_vector_animation) self.play( FadeOut(vectors), VFadeIn(moving_vector) ) self.wait(10) vectors.set_fill(opacity=0.5) self.play( LaggedStartMap(ShowCreation, vectors), Animation(moving_vector) ) self.wait(5) self.velocity_vectors = vectors self.moving_vector = moving_vector def collect_velocity_vectors(self): vectors = self.velocity_vectors.copy() frame = self.camera_frame ellipse = self.ellipse frame_shift = 2.5 * LEFT root_point = ellipse.get_left() + 3 * LEFT + 1 * UP vector_targets = VGroup() for vector in vectors: vector.target = Arrow( root_point, root_point + vector.get_vector(), buff=0, rectangular_stem_width=0.025, tip_length=0.2, color=vector.get_color(), ) vector.target.add_to_back( vector.target.copy().set_stroke(BLACK, 5) ) vector_targets.add(vector.target) circle = Circle(color=YELLOW) circle.replace(vector_targets) circle.scale(1.04) velocity_space = TextMobject("Velocity space") velocity_space.next_to(circle, UP) rect = SurroundingRectangle( VGroup(circle, velocity_space), buff=MED_LARGE_BUFF, color=WHITE, ) self.play( ApplyMethod( frame.shift, frame_shift, run_time=2, ), LaggedStartMap( MoveToTarget, vectors, run_time=4, ), FadeInFromDown(velocity_space), FadeInFromDown(rect), ) self.wait(2) self.play( ShowCreation(circle), Animation(vectors) ) self.wait(24) # Helpers def get_velocity_vector(self, alpha, d_alpha=0.01, scalar=3.0): norm = get_norm ellipse = self.ellipse sun_center = self.sun.get_center() min_length = 0.1 * scalar max_length = 0.5 * scalar p1, p2 = [ ellipse.point_from_proportion(a) for a in (alpha, alpha + d_alpha) ] vector = Arrow( p1, p2, buff=0 ) radius_vector = p1 - sun_center curr_v_perp = norm(np.cross( vector.get_vector(), radius_vector / norm(radius_vector) )) vector.scale( scalar / (norm(curr_v_perp) * norm(radius_vector)), about_point=vector.get_start() ) vector.set_color( interpolate_color( BLUE, RED, inverse_interpolate( min_length, max_length, vector.get_length() ) ) ) vector.add_to_back( vector.copy().set_stroke(BLACK, 5) ) return vector def get_velocity_vector_and_update(self): moving_vector = self.get_velocity_vector(0) def update_moving_vector(vector): new_vector = self.get_velocity_vector( self.orbit.proportion, ) Transform(vector, new_vector).update(1) moving_vector_animation = Mobject.add_updater( moving_vector, update_moving_vector ) return moving_vector, moving_vector_animation class AskWhy(TeacherStudentsScene): def construct(self): self.student_says( "Um...why?", target_mode="confused", student_index=2, bubble_kwargs={"direction": LEFT}, ) self.play( self.teacher.change, "happy", self.get_student_changes( "raise_left_hand", "sassy", "confused" ) ) self.wait(5) class FeynmanConfusedByNewton(Scene): def construct(self): pass class ShowEqualAngleSlices(IntroduceShapeOfVelocities): CONFIG = { "animate_sun": True, "theta": 30 * DEGREES, } def construct(self): self.setup_orbit() self.show_equal_angle_slices() self.ask_about_time_per_slice() self.areas_are_proportional_to_radius_squared() self.show_inverse_square_law() self.directly_compare_velocity_vectors() def setup_orbit(self): IntroduceShapeOfVelocities.setup_orbit(self) self.remove(self.orbit) self.add(self.comet) def show_equal_angle_slices(self): sun_center = self.sun.get_center() ellipse = self.ellipse def get_cos_angle_diff(v1, v2): return np.dot( v1 / get_norm(v1), v2 / get_norm(v2), ) lines = VGroup() angle_arcs = VGroup() thetas = VGroup() angles = np.arange(0, TAU, self.theta) for angle in angles: prop = angle / TAU vect = rotate_vector(RIGHT, angle) end_point = ellipse.point_from_proportion(prop) curr_cos = get_cos_angle_diff( end_point - sun_center, vect ) coss_diff = (1 - curr_cos) while abs(coss_diff) > 0.00001: d_prop = 0.001 alt_end = ellipse.point_from_proportion( (prop + d_prop) % 1 ) alt_cos = get_cos_angle_diff(alt_end - sun_center, vect) d_cos = (alt_cos - curr_cos) delta_prop = (coss_diff / d_cos) * d_prop prop += delta_prop end_point = ellipse.point_from_proportion(prop) curr_cos = get_cos_angle_diff(end_point - sun_center, vect) coss_diff = 1 - curr_cos line = Line(sun_center, end_point) line.prop = prop lines.add(line) angle_arc = AnnularSector( angle=self.theta, inner_radius=1, outer_radius=1.05, ) angle_arc.rotate(angle, about_point=ORIGIN) angle_arc.scale(0.5, about_point=ORIGIN) angle_arc.shift(sun_center) angle_arc.mid_angle = angle + self.theta / 2 angle_arcs.add(angle_arc) theta = TexMobject("\\theta") theta.scale(0.6) vect = rotate_vector(RIGHT, angle_arc.mid_angle) theta.move_to( angle_arc.get_center() + 0.2 * vect ) thetas.add(theta) arcs = VGroup() wedges = VGroup() for l1, l2 in adjacent_pairs(lines): arc = VMobject() arc.pointwise_become_partial( ellipse, l1.prop, (l2.prop or 1.0) ) arcs.add(arc) wedge = VMobject() wedge.append_vectorized_mobject( Line(sun_center, arc.points[0]) ) wedge.append_vectorized_mobject(arc) wedge.append_vectorized_mobject( Line(arc.points[-1], sun_center) ) wedges.add(wedge) lines.set_stroke(LIGHT_GREY, 2) angle_arcs.set_color_by_gradient( YELLOW, BLUE, RED, PINK, YELLOW ) arcs.set_color_by_gradient(BLUE, YELLOW) wedges.set_stroke(width=0) wedges.set_fill(opacity=1) wedges.set_color_by_gradient(BLUE, COBALT, BLUE_E, BLUE) kwargs = { "run_time": 6, "lag_ratio": 0.2, "rate_func": there_and_back, } faders = VGroup(wedges, angle_arcs, thetas) faders.set_fill(opacity=0.4) thetas.set_fill(opacity=0) self.play( FadeIn(faders), *list(map(ShowCreation, lines)) ) self.play(*[ LaggedStartMap( ApplyMethod, fader, lambda m: (m.set_fill, {"opacity": 1}), **kwargs ) for fader in faders ] + [Animation(lines)]) self.wait() self.lines = lines self.wedges = wedges self.arcs = arcs self.angle_arcs = angle_arcs self.thetas = thetas def ask_about_time_per_slice(self): wedge1 = self.wedges[0] wedge2 = self.wedges[len(self.wedges) / 2] arc1 = self.arcs[0] arc2 = self.arcs[len(self.arcs) / 2] comet = self.comet frame = self.camera_frame words1 = TextMobject( "Time spent \\\\ traversing \\\\ this slice?" ) words2 = TextMobject("How about \\\\ this one?") words1.to_corner(UR) words2.next_to(wedge2, LEFT, MED_LARGE_BUFF) arrow1 = Arrow( words1.get_bottom(), wedge1.get_center() + wedge1.get_height() * DOWN / 2, color=WHITE, ) arrow2 = Arrow( words2.get_right(), wedge2.get_center() + wedge2.get_height() * UL / 4, color=WHITE ) foreground = VGroup( self.ellipse, self.angle_arcs, self.lines, comet, ) self.play( Write(words1), wedge1.set_fill, {"opacity": 1}, GrowArrow(arrow1), Animation(foreground), frame.scale, 1.2, ) self.play(MoveAlongPath(comet, arc1, rate_func=linear)) self.play( Write(words2), wedge2.set_fill, {"opacity": 1}, Write(arrow2), Animation(foreground), ) self.play(MoveAlongPath(comet, arc2, rate_func=linear, run_time=3)) self.wait() self.area_questions = VGroup(words1, words2) self.area_question_arrows = VGroup(arrow1, arrow2) self.highlighted_wedges = VGroup(wedge1, wedge2) def areas_are_proportional_to_radius_squared(self): wedges = self.highlighted_wedges wedge = wedges[1] frame = self.camera_frame ellipse = self.ellipse sun_center = self.sun.get_center() line = self.lines[len(self.lines) / 2] thick_line = line.copy().set_stroke(PINK, 4) radius_word = TextMobject("Radius") radius_word.next_to(thick_line, UP, SMALL_BUFF) radius_word.match_color(thick_line) arc = self.arcs[len(self.arcs) / 2] thick_arc = arc.copy().set_stroke(RED, 4) scaling_group = VGroup( wedge, thick_line, radius_word, thick_arc ) expression = TextMobject( "Time", "$\\propto$", "Area", "$\\propto$", "$(\\text{Radius})^2$" ) expression.next_to(ellipse, UP, LARGE_BUFF) prop_to_brace = Brace(expression[1], DOWN, buff=SMALL_BUFF) prop_to_words = TextMobject("(proportional to)") prop_to_words.scale(0.7) prop_to_words.next_to(prop_to_brace, DOWN, SMALL_BUFF) VGroup(prop_to_words, prop_to_brace).set_color(GREEN) self.play( Write(expression[:3]), frame.shift, 0.5 * UP, FadeInFromDown(prop_to_words), GrowFromCenter(prop_to_brace), ) self.wait(2) self.play( ShowCreation(thick_line), FadeInFromDown(radius_word) ) self.wait() self.play(ShowCreationThenDestruction(thick_arc)) self.play(ShowCreation(thick_arc)) self.wait() self.play(Write(expression[3:])) self.play( scaling_group.scale, 0.5, {"about_point": sun_center}, Animation(self.area_question_arrows), Animation(self.comet), rate_func=there_and_back, run_time=4, ) self.wait() expression.add(prop_to_brace, prop_to_words) self.proportionality_expression = expression def show_inverse_square_law(self): prop_exp = self.proportionality_expression comet = self.comet frame = self.camera_frame ellipse = self.ellipse orbit = self.orbit next_line = self.lines[(len(self.lines) / 2) + 1] arc = self.arcs[len(self.arcs) / 2] force_expression = TexMobject( "ma", "=", "\\text{Force}", "\\propto", "\\frac{1}{(\\text{Radius})^2}" ) force_expression.next_to(ellipse, LEFT, MED_LARGE_BUFF) force_expression.align_to(prop_exp, UP) force_expression.set_color_by_tex("Force", YELLOW) acceleration_expression = TexMobject( "a", "=", "{\\Delta v", "\\over", "\\Delta t}", "\\propto", "{1 \\over (\\text{Radius})^2}" ) acceleration_expression.next_to( force_expression, DOWN, buff=0.75, aligned_edge=LEFT ) delta_v_expression = TexMobject( "\\Delta v}", "\\propto", "{\\Delta t", "\\over", "(\\text{Radius})^2}" ) delta_v_expression.next_to( acceleration_expression, DOWN, buff=0.75, aligned_edge=LEFT ) delta_t_numerator = delta_v_expression.get_part_by_tex( "\\Delta t" ) moving_R_squared = prop_exp.get_part_by_tex("Radius").copy() moving_R_squared.generate_target() moving_R_squared.target.move_to(delta_t_numerator, DOWN) moving_R_squared.target.set_color(GREEN) randy = Randolph() randy.next_to(force_expression, DOWN, LARGE_BUFF) force_vector, force_vector_update = self.get_force_arrow_and_update( comet, scale_factor=3, ) moving_vector, moving_vector_animation = self.get_velocity_vector_and_update() self.play( FadeOut(self.area_questions), FadeOut(self.area_question_arrows), FadeInFromDown(force_expression), frame.shift, 2 * LEFT, ) self.remove(*self.area_questions) self.play( randy.change, "confused", force_expression, VFadeIn(randy) ) self.wait(2) self.play( randy.change, "pondering", force_expression[0], ShowPassingFlashAround(force_expression[:2]), ) self.play(Blink(randy)) self.play( FadeInFromDown(acceleration_expression), randy.change, "hooray", force_expression, randy.shift, 2 * DOWN, ) self.wait(2) self.play(Blink(randy)) self.play(randy.change, "thinking") self.wait() self.play( comet.move_to, arc.points[0], path_arc=90 * DEGREES ) force_vector_update.update(0) self.play( Blink(randy), GrowArrow(force_vector) ) self.add(force_vector_update) self.add_foreground_mobjects(comet) # Slightly hacky orbit treatment here... orbit.proportion = 0.5 moving_vector_animation.update(0) start_velocity_vector = moving_vector.copy() self.play( GrowArrow(start_velocity_vector), randy.look_at, moving_vector ) self.add(moving_vector_animation) self.add(orbit) while orbit.proportion < next_line.prop: self.wait(self.frame_duration) self.remove(orbit) self.add_foreground_mobjects(comet) self.wait(2) self.play( randy.change, "pondering", force_expression, randy.shift, 2 * DOWN, FadeInFromDown(delta_v_expression) ) self.play(Blink(randy)) self.wait(2) self.play( delta_t_numerator.scale, 1.5, {"about_edge": DOWN}, delta_t_numerator.set_color, YELLOW ) self.play(ShowCreationThenFadeAround(prop_exp[:-2])) self.play( delta_t_numerator.fade, 1, MoveToTarget(moving_R_squared), randy.change, "happy", delta_v_expression ) delta_v_expression.add(moving_R_squared) self.wait() self.play(FadeOut(randy)) self.start_velocity_vector = start_velocity_vector self.end_velocity_vector = moving_vector.copy() self.moving_vector = moving_vector self.force_expressions = VGroup( force_expression, acceleration_expression, delta_v_expression, ) def directly_compare_velocity_vectors(self): ellipse = self.ellipse lines = self.lines expressions = self.force_expressions vectors = VGroup(*[ self.get_velocity_vector(line.prop) for line in lines ]) index = len(vectors) / 2 v1 = vectors[index] v2 = vectors[index + 1] root_point = ellipse.get_left() + 3 * LEFT + DOWN root_dot = Dot(root_point) for vector in vectors: vector.save_state() vector.target = Arrow( *vector.get_start_and_end(), color=vector.get_color(), buff=0 ) vector.target.scale(2) vector.target.shift( root_point - vector.target.get_start() ) vector.target.add_to_back( vector.target.copy().set_stroke(BLACK, 5) ) difference_vectors = VGroup() external_angle_lines = VGroup() external_angle_arcs = VGroup() for vect1, vect2 in adjacent_pairs(vectors): diff_vect = Arrow( vect1.target.get_end(), vect2.target.get_end(), buff=0, color=YELLOW, rectangular_stem_width=0.025, tip_length=0.15 ) diff_vect.add_to_back( diff_vect.copy().set_stroke(BLACK, 2) ) difference_vectors.add(diff_vect) line = Line( diff_vect.get_start(), diff_vect.get_start() + 2 * diff_vect.get_vector(), ) external_angle_lines.add(line) arc = Arc(self.theta, stroke_width=2) arc.rotate(line.get_angle(), about_point=ORIGIN) arc.scale(0.4, about_point=ORIGIN) arc.shift(line.get_center()) external_angle_arcs.add(arc) external_angle_lines.set_stroke(LIGHT_GREY, 2) diff_vect = difference_vectors[index] polygon = Polygon(*[ vect.target.get_end() for vect in vectors ]) polygon.set_fill(BLUE_E, opacity=0.8) polygon.set_stroke(WHITE, 3) self.play(ShowCreationThenFadeAround(v1)) self.play( MoveToTarget(v1), GrowFromCenter(root_dot), expressions.scale, 0.5, {"about_edge": UL} ) self.wait() self.play( ReplacementTransform( v1.saved_state.copy(), v2.saved_state, path_arc=self.theta ) ) self.play(MoveToTarget(v2), Animation(root_dot)) self.wait() self.play(GrowArrow(diff_vect)) self.wait() n = len(vectors) for i in range(n - 1): v1 = vectors[(i + index + 1) % n] v2 = vectors[(i + index + 2) % n] diff_vect = difference_vectors[(i + index + 1) % n] # TODO, v2.saved_state is on screen untracked self.play(ReplacementTransform( v1.saved_state.copy(), v2.saved_state, path_arc=self.theta )) self.play( MoveToTarget(v2), GrowArrow(diff_vect) ) self.add(self.orbit) self.wait() self.play( LaggedStartMap(ShowCreation, external_angle_lines), LaggedStartMap(ShowCreation, external_angle_arcs), Animation(difference_vectors), ) self.add_foreground_mobjects(difference_vectors) self.wait(2) self.play(FadeIn(polygon)) self.wait(5) self.play(FadeOut(polygon)) self.wait(15) self.play(FadeIn(polygon)) class ShowEqualAngleSlices15DegreeSlices(ShowEqualAngleSlices): CONFIG = { "animate_sun": True, "theta": 15 * DEGREES, } class ShowEqualAngleSlices5DegreeSlices(ShowEqualAngleSlices): CONFIG = { "animate_sun": True, "theta": 5 * DEGREES, } class IKnowThisIsTricky(TeacherStudentsScene): def construct(self): self.teacher_says( "All you need is \\\\ infinite intelligence", bubble_kwargs={ "width": 4, "height": 3, }, added_anims=[ self.get_student_changes( *3 * ["horrified"], look_at_arg=self.screen ) ] ) self.look_at(self.screen) self.wait(3) class PonderOverOffCenterDiagram(PiCreatureScene): def construct(self): randy, morty = self.pi_creatures velocity_diagram = self.get_velocity_diagram() bubble = randy.get_bubble() rect = SurroundingRectangle( velocity_diagram, buff=MED_LARGE_BUFF, color=LIGHT_GREY ) rect.stretch(1.2, 1, about_edge=DOWN) words = TextMobject("Velocity space") words.next_to(rect.get_top(), DOWN) self.play( LaggedStartMap(GrowFromCenter, velocity_diagram), randy.change, "pondering", morty.change, "confused", ) self.wait(2) self.play(ShowCreation(bubble)) self.wait(2) self.play( FadeOut(bubble), randy.change, "confused", morty.change, "pondering", ShowCreation(rect) ) self.play(Write(words)) self.wait(2) def create_pi_creatures(self): randy = Randolph(height=2.5) randy.to_corner(DL) morty = Mortimer(height=2.5) morty.to_corner(DR) return randy, morty def get_velocity_diagram(self): circle = Circle(color=WHITE, radius=2) circle.rotate(90 * DEGREES) circle.to_edge(DOWN, buff=LARGE_BUFF) root_point = interpolate( circle.get_center(), circle.get_bottom(), 0.5, ) dot = Dot(root_point) vectors = VGroup() for a in np.arange(0, 1, 1.0 / 24): end_point = circle.point_from_proportion(a) vector = Arrow(root_point, end_point, buff=0) vector.set_color(interpolate_color( BLUE, RED, inverse_interpolate( 1, 3, vector.get_length(), ) )) vector.add_to_back(vector.copy().set_stroke(BLACK, 5)) vectors.add(vector) vectors.add_to_back(circle) vectors.add(dot) return vectors class OneMoreTrick(TeacherStudentsScene): def construct(self): for student in self.students: student.change("tired") self.teacher_says("Just one more \\\\ tricky bit!") self.change_all_student_modes("hooray") self.wait(3) class UseVelocityDiagramToDeduceCurve(ShowEqualAngleSlices): CONFIG = { "animate_sun": True, "theta": 15 * DEGREES, "index": 6, } def construct(self): self.setup_orbit() self.setup_velocity_diagram() self.show_theta_degrees() self.match_velocity_vector_to_tangency() self.replace_vectors_with_lines() self.not_that_velocity_vector_is_theta() self.ask_about_curve() self.show_90_degree_rotation() self.show_geometry_of_rotated_diagram() def setup_orbit(self): ShowEqualAngleSlices.setup_orbit(self) self.force_skipping() self.show_equal_angle_slices() self.revert_to_original_skipping_status() orbit_word = self.orbit_word = TextMobject("Orbit") orbit_word.scale(1.5) orbit_word.next_to(self.ellipse, UP, LARGE_BUFF) self.add(orbit_word) def setup_velocity_diagram(self): ellipse = self.ellipse root_point = ellipse.get_left() + 4 * LEFT + DOWN frame = self.camera_frame root_dot = Dot(root_point) vectors = VGroup() original_vectors = VGroup() for line in self.lines: vector = self.get_velocity_vector(line.prop) vector.save_state() original_vectors.add(vector.copy()) vector.target = self.get_velocity_vector( line.prop, scalar=8.0 ) vector.target.shift( root_point - vector.target.get_start() ) vectors.add(vector) circle = Circle() circle.rotate(92 * DEGREES) circle.replace(VGroup(*[v.target for v in vectors])) circle.set_stroke(WHITE, 2) circle.shift( (root_point[0] - circle.get_center()[0]) * RIGHT ) circle.shift(0.035 * LEFT) # ?!? velocities_word = TextMobject("Velocities") velocities_word.scale(1.5) velocities_word.next_to(circle, UP) velocities_word.align_to(self.orbit_word, DOWN) frame.scale(1.2) frame.shift(3 * LEFT + 0.5 * UP) self.play(ApplyWave(ellipse)) self.play(*list(map(GrowArrow, vectors))) self.play( LaggedStartMap( MoveToTarget, vectors, lag_ratio=1, run_time=2 ), GrowFromCenter(root_dot), FadeInFromDown(velocities_word), ) self.add_foreground_mobjects(root_dot) self.play( ShowCreation(circle), Animation(vectors), ) self.wait() self.vectors = vectors self.original_vectors = original_vectors self.velocity_circle = circle self.root_dot = root_dot self.circle = circle def show_theta_degrees(self): lines = self.lines ellipse = self.ellipse circle = self.circle vectors = self.vectors comet = self.comet sun_center = self.sun.get_center() index = self.index angle = fdiv(index, len(lines)) * TAU thick_line = lines[index].copy() thick_line.set_stroke(RED, 3) horizontal = lines[0].copy() horizontal.set_stroke(WHITE, 3) ellipse_arc = VMobject() ellipse_arc.pointwise_become_partial( ellipse, 0, thick_line.prop ) ellipse_arc.set_stroke(YELLOW, 3) ellipse_wedge = self.get_wedge(ellipse_arc, sun_center) ellipse_wedge_start = self.get_wedge( VectorizedPoint(ellipse.get_right()), sun_center ) ellipse_angle_arc = Arc( self.theta * index, radius=0.5 ) ellipse_angle_arc.shift(sun_center) ellipse_theta = TexMobject("\\theta") ellipse_theta.next_to(ellipse_angle_arc, RIGHT, MED_SMALL_BUFF) ellipse_theta.shift(2 * SMALL_BUFF * UL) vector = vectors[index].deepcopy() vector.set_fill(YELLOW) vector.save_state() Transform(vector, vectors[0]).update(1) vector.set_fill(YELLOW) circle_arc = VMobject() circle_arc.pointwise_become_partial( circle, 0, fdiv(index, len(vectors)) ) circle_arc.set_stroke(RED, 4) circle_theta = TexMobject("\\theta") circle_theta.scale(1.5) circle_theta.next_to(circle_arc, UP, SMALL_BUFF) circle_theta.shift(SMALL_BUFF * DL) circle_wedge = self.get_wedge(circle_arc, circle.get_center()) circle_wedge.set_fill(PINK) circle_wedge_start = self.get_wedge( Line(circle.get_top(), circle.get_top()), circle.get_center() ).match_style(circle_wedge) circle_center_dot = Dot(circle.get_center()) # circle_center_dot.set_color(BLUE) self.play(FocusOn(comet)) self.play( ReplacementTransform( ellipse_wedge_start, ellipse_wedge, path_arc=angle, ), FadeIn(ellipse_arc), ShowCreation(ellipse_angle_arc), Write(ellipse_theta), ReplacementTransform( lines[0].copy(), thick_line, path_arc=angle ), MoveAlongPath(comet, ellipse_arc), run_time=2 ) self.wait() self.play( ReplacementTransform( circle_wedge_start, circle_wedge, path_arc=angle ), ShowCreation(circle_arc), Write(circle_theta), Restore(vector, path_arc=angle), GrowFromCenter(circle_center_dot), FadeIn(horizontal), run_time=2 ) self.wait() self.set_variables_as_attrs( index, ellipse_wedge, ellipse_arc, ellipse_angle_arc, ellipse_theta, thick_line, horizontal, circle_wedge, circle_arc, circle_theta, circle_center_dot, highlighted_vector=vector ) def match_velocity_vector_to_tangency(self): vector = self.highlighted_vector comet = self.comet original_vector = self.original_vectors[self.index].copy() original_vector.set_fill(YELLOW) tangent_line = Line( *original_vector.get_start_and_end() ) tangent_line.set_stroke(LIGHT_GREY, 3) tangent_line.scale(5) tangent_line.move_to(comet) self.play( ReplacementTransform( vector.copy(), original_vector, run_time=2 ), Animation(comet), ) self.wait() self.play( ShowCreation(tangent_line), Animation(original_vector), Animation(comet), ) self.wait() self.set_variables_as_attrs( example_tangent_line=tangent_line, example_tangent_vector=original_vector, ) def replace_vectors_with_lines(self): vectors = self.vectors original_vectors = self.original_vectors root_dot = self.root_dot highlighted_vector = self.highlighted_vector lines = VGroup() tangent_lines = VGroup() for vect, o_vect in zip(vectors, original_vectors): line = Line(*vect.get_start_and_end()) t_line = Line(*o_vect.get_start_and_end()) t_line.scale(5) t_line.move_to(o_vect.get_start()) lines.add(line) tangent_lines.add(t_line) vect.generate_target() vect.target.scale(0, about_point=root_dot.get_center()) lines.set_stroke(GREEN, 2) tangent_lines.set_stroke(GREEN, 2) highlighted_line = Line( *highlighted_vector.get_start_and_end(), stroke_color=YELLOW, stroke_width=4 ) self.play( LaggedStartMap(MoveToTarget, vectors), highlighted_vector.scale, 0, {"about_point": root_dot.get_center()}, Animation(highlighted_vector), Animation(self.circle_wedge), Animation(self.circle_arc), Animation(self.circle), Animation(self.circle_center_dot), ) self.remove(vectors, highlighted_vector) self.play( LaggedStartMap(ShowCreation, lines), ShowCreation(highlighted_line), Animation(highlighted_vector), ) self.wait() self.play( ReplacementTransform( lines.copy(), tangent_lines, run_time=3, ) ) self.wait() self.play(FadeOut(tangent_lines)) self.eccentric_lines = lines self.highlighted_line = highlighted_line def not_that_velocity_vector_is_theta(self): vector = self.example_tangent_vector v_line = Line(DOWN, UP) v_line.move_to(vector.get_start(), DOWN) angle = vector.get_angle() - 90 * DEGREES arc = Arc(angle, radius=0.5) arc.rotate(90 * DEGREES, about_point=ORIGIN) arc.shift(vector.get_start()) theta_q = TexMobject("\\theta ?") theta_q.next_to(arc, UP) theta_q.shift(SMALL_BUFF * LEFT) cross = Cross(theta_q) self.play(ShowCreation(v_line)) self.play( ShowCreation(arc), FadeInFromDown(theta_q), ) self.wait() self.play(ShowCreation(cross)) self.wait() self.play(*list(map(FadeOut, [v_line, arc, theta_q, cross]))) self.wait() self.play( ReplacementTransform( self.ellipse_theta.copy(), self.circle_theta, ), ReplacementTransform( self.ellipse_angle_arc.copy(), self.circle_arc, ), run_time=2, ) self.wait() self.play( ReplacementTransform( self.circle.copy(), self.circle_center_dot, ) ) self.wait() def ask_about_curve(self): ellipse = self.ellipse circle = self.circle line = self.highlighted_line.copy() vector = self.example_tangent_vector morty = Mortimer(height=2.5) morty.move_to(ellipse.get_corner(UL)) morty.shift(MED_SMALL_BUFF * LEFT) self.play(FadeIn(morty)) self.play( morty.change, "confused", ellipse, ShowCreationThenDestruction( ellipse.copy().set_stroke(BLUE, 3), run_time=2 ) ) self.play( Blink(morty), ApplyWave(ellipse), ) self.play(morty.look_at, circle) self.play(morty.change, "pondering", circle) self.play(Blink(morty)) self.play(morty.look_at, ellipse) self.play(morty.change, "maybe", ellipse) self.play( line.move_to, vector, run_time=2 ) self.play(FadeOut(line)) self.wait() self.play(morty.look_at, circle) self.wait() self.play(FadeOut(morty)) def show_90_degree_rotation(self): circle = self.circle circle_setup = VGroup( circle, self.eccentric_lines, self.circle_wedge, self.circle_arc, self.highlighted_line, self.circle_center_dot, self.root_dot, self.circle_theta, ) circle_setup.generate_target() angle = -90 * DEGREES circle_setup.target.rotate( angle, about_point=circle.get_center() ) circle_setup.target[-1].rotate(-angle) circle_setup.target[2].set_fill(opacity=0) circle_setup.target[2].set_stroke(WHITE, 4) self.play(MoveToTarget(circle_setup, path_arc=angle)) self.wait() lines = self.eccentric_lines highlighted_line = self.highlighted_line ghost_lines = lines.copy() ghost_lines.set_stroke(width=1) ghost_lines[self.index].set_stroke(YELLOW, 4) for mob in list(lines) + [highlighted_line]: mob.generate_target() mob.save_state() mob.target.rotate(-angle) foci = [ self.root_dot.get_center(), circle.get_center(), ] a = circle.get_width() / 4 c = get_norm(foci[1] - foci[0]) / 2 b = np.sqrt(a**2 - c**2) little_ellipse = Circle(radius=a) little_ellipse.stretch(b / a, 1) little_ellipse.move_to(center_of_mass(foci)) little_ellipse.set_stroke(PINK, 4) self.add(ghost_lines) self.play( LaggedStartMap( MoveToTarget, lines, lag_ratio=0.1, run_time=8, ), MoveToTarget(highlighted_line), path_arc=-angle, ) self.play(ShowCreation(little_ellipse)) self.wait(2) self.play( little_ellipse.replace, self.ellipse, run_time=4, rate_func=there_and_back_with_pause ) self.wait(2) self.play(*[ Restore( mob, path_arc=angle, run_time=4, rate_func=there_and_back_with_pause ) for mob in list(lines) + [highlighted_line] ] + [Animation(little_ellipse)]) self.ghost_lines = ghost_lines self.little_ellipse = little_ellipse def show_geometry_of_rotated_diagram(self): ellipse = self.ellipse little_ellipse = self.little_ellipse circle = self.circle perp_line = self.highlighted_line.copy() perp_line.rotate(PI) circle_arc = self.circle_arc arc_copy = circle_arc.copy() center = circle.get_center() velocity_vector = self.example_tangent_vector e_line = perp_line.copy().rotate(90 * DEGREES) c_line = Line(center, e_line.get_end()) c_line.set_stroke(WHITE, 4) # lines = [c_line, e_line, perp_line] tangency_point = line_intersection( perp_line.get_start_and_end(), c_line.get_start_and_end(), ) tangency_point_dot = Dot(tangency_point) tangency_point_dot.set_color(BLUE) tangency_point_dot.save_state() tangency_point_dot.scale(5) tangency_point_dot.set_fill(opacity=0) def indicate(line): red_copy = line.copy().set_stroke(RED, 5) return ShowPassingFlash(red_copy, run_time=2) self.play( self.ghost_lines.set_stroke, {"width": 0.5}, self.eccentric_lines.set_stroke, {"width": 0.5}, *list(map(WiggleOutThenIn, [e_line, c_line])) ) for x in range(3): self.play( indicate(e_line), indicate(c_line), ) self.play(WiggleOutThenIn(perp_line)) for x in range(2): self.play(indicate(perp_line)) self.play(Restore(tangency_point_dot)) self.add_foreground_mobjects(tangency_point_dot) self.wait(2) self.play( arc_copy.scale, 0.15, {"about_point": center}, run_time=2 ) self.wait(2) self.play( perp_line.move_to, velocity_vector, run_time=4, rate_func=there_and_back_with_pause ) self.wait() self.play( little_ellipse.replace, ellipse, run_time=4, rate_func=there_and_back_with_pause ) self.wait() # Helpers def get_wedge(self, arc, center_point, opacity=0.8): wedge = VMobject() wedge.append_vectorized_mobject( Line(center_point, arc.points[0]) ) wedge.append_vectorized_mobject(arc) wedge.append_vectorized_mobject( Line(arc.points[-1], center_point) ) wedge.set_stroke(width=0) wedge.set_fill(COBALT, opacity=opacity) return wedge class ShowSunVectorField(Scene): def construct(self): sun_center = IntroduceShapeOfVelocities.CONFIG["sun_center"] vector_field = VectorField( get_force_field_func((sun_center, -1)) ) vector_field.set_fill(opacity=0.8) vector_field.sort( lambda p: -get_norm(p - sun_center) ) for vector in vector_field: vector.generate_target() vector.target.set_fill(opacity=1) vector.target.set_stroke(YELLOW, 0.5) for x in range(3): self.play(LaggedStartMap( MoveToTarget, vector_field, rate_func=there_and_back, lag_ratio=0.5, )) class TryToRememberProof(PiCreatureScene): def construct(self): randy = self.pi_creature words = TextMobject("Oh god...how \\\\ did it go?") words.next_to(randy, UP) words.shift_onto_screen() self.play( randy.change, "telepath", FadeInFromDown(words) ) self.look_at(ORIGIN) self.wait(2) self.play(randy.change, "concerned_musician") self.look_at(ORIGIN) self.wait(3) class PatYourselfOnTheBack(TeacherStudentsScene): CONFIG = { "camera_config": {"background_opacity": 1}, } def construct(self): self.teacher_says( "Pat yourself \\\\ on the back!", target_mode="hooray" ) self.change_all_student_modes("happy") self.wait(3) self.play( RemovePiCreatureBubble( self.teacher, target_mode="raise_right_hand" ), self.get_student_changes( *3 * ["pondering"], look_at_arg=self.screen ) ) self.look_at(UP) self.wait(8) self.change_student_modes(*3 * ["thinking"]) self.look_at(UP) self.wait(12) self.teacher_says("I just love this!") feynman = ImageMobject("Feynman", height=4) feynman.to_corner(UL) chess = ImageMobject("ChessGameOfTheCentury") chess.set_height(4) chess.next_to(feynman) self.play(FadeInFromDown(feynman)) self.wait() self.play( RemovePiCreatureBubble(self.teacher, target_mode="happy"), FadeInFromDown(chess) ) self.wait(2) class Thumbnail(ShowEmergingEllipse): CONFIG = { "num_lines": 50, } def construct(self): background = ImageMobject("Feynman_teaching") background.set_width(FRAME_WIDTH) background.scale(1.05) background.to_corner(UR, buff=0) background.shift(2 * UP) self.add(background) circle = self.get_circle() circle.set_stroke(width=6) circle.set_height(6.5) circle.to_corner(UL) circle.set_fill(BLACK, 0.9) lines = self.get_lines() lines.set_stroke(YELLOW, 5) lines.set_color_by_gradient(YELLOW, RED) ghost_lines = self.get_ghost_lines(lines) for line in lines: line.rotate(90 * DEGREES) ellipse = self.get_ellipse() ellipse.set_stroke(BLUE, 6) sun = ImageMobject("sun", height=0.5) sun.move_to(self.get_eccentricity_point()) circle_group = VGroup(circle, ghost_lines, lines, ellipse, sun) self.add(circle_group) l1 = Line( circle.point_from_proportion(0.175), 6.25 * RIGHT + 0.75 * DOWN ) l2 = Line( circle.point_from_proportion(0.75), 6.25 * RIGHT + 2.5 * DOWN ) l2a = VMobject().pointwise_become_partial(l2, 0, 0.56) l2b = VMobject().pointwise_become_partial(l2, 0.715, 1) expand_lines = VGroup(l1, l2a, l2b) expand_lines.set_stroke("RED", 5) self.add(expand_lines) self.add(circle_group) small_group = circle_group.copy() small_group.scale(0.2) small_group.stretch(1.35, 1) small_group.move_to(6.2 * RIGHT + 1.6 * DOWN) for mob in small_group: if isinstance(mob, VMobject) and mob.get_stroke_width() > 1: mob.set_stroke(width=1) small_group[0].set_fill(opacity=0.25) self.add(small_group) title = TextMobject( "Feynman's \\\\", "Lost \\\\", "Lecture", alignment="" ) title.scale(2.4) for part in title: part.add_to_back( part.copy().set_stroke(BLACK, 12).set_fill(BLACK, 1) ) title.to_corner(UR) title[2].to_edge(RIGHT) title[1].shift(0.9 * RIGHT) title.shift(0.5 * LEFT) self.add(title)