diff --git a/active_projects/lost_lecture.py b/active_projects/lost_lecture.py index 1d004350..f7cd3e77 100644 --- a/active_projects/lost_lecture.py +++ b/active_projects/lost_lecture.py @@ -2,51 +2,418 @@ from __future__ import absolute_import from big_ol_pile_of_manim_imports import * +class Orbiting(ContinualAnimation): + CONFIG = { + "rate": 0.3, + } + + def __init__(self, planet, star, ellipse, **kwargs): + self.planet = planet + self.star = star + self.ellipse = ellipse + # Proportion of the way around the ellipse + self.proportion = 0 + ContinualAnimation.__init__(self, planet, **kwargs) + + def update_mobject(self, dt): + # time = self.internal_time + rate = self.rate + + planet = self.planet + star = self.star + ellipse = self.ellipse + + rate *= 1 / np.linalg.norm( + planet.get_center() - star.get_center() + ) + self.proportion += rate * dt + self.proportion = self.proportion % 1 + planet.move_to(ellipse.point_from_proportion(self.proportion)) + +# Animations + + class ShowEmergingEllipse(Scene): + CONFIG = { + "circle_radius": 3, + "circle_color": BLUE, + "num_lines": 100, + "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 = Circle(num_anchors=50, radius=3, color=BLUE) - e_point = 2 * RIGHT + circle = self.get_circle() + e_point = self.get_eccentricity_point() e_dot = Dot(e_point, color=YELLOW) - lines = VGroup(*[ - Line(e_point, circle.point_from_proportion(a)) - for a in np.linspace(0, 1, 4 * 49) - ]) - lines.set_stroke(width=1) - for line in lines: - line.generate_target() - line.target.rotate(90 * DEGREES) + lines = self.get_lines() + ellipse = self.get_ellipse() fade_rect = FullScreenFadeRectangle() - line = lines[20] + + line = lines[len(lines) / 5] line_dot = Dot(line.get_center(), color=YELLOW) + line_dot.scale(0.5) - words = TextMobject("Rotate $90^\\circ$ \\\\ about center") - words.add_to_back(words.copy().set_stroke(BLACK, 2)) - words.next_to(line_dot, RIGHT) + ghost_line = self.get_ghost_lines(line) + ghost_lines = self.get_ghost_lines(lines) - self.play( - LaggedStart(ShowCreation, lines), - Animation(VGroup(e_dot, circle)) + rot_words = TextMobject("Rotate $90^\\circ$ \\\\ about center") + rot_words.next_to(line_dot, RIGHT) + + 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 ) - self.add(lines.copy().set_stroke(LIGHT_GREY, 0.5)) + elbow.shift(line.get_center()) + + 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( + FadeInAndShiftFromDirection(e_dot, LEFT), + Write(eccentric_words, run_time=1) + ) + self.wait() + self.play( + LaggedStart(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)) self.play( + FadeIn(fade_rect), + Animation(line), GrowFromCenter(line_dot), - FadeInFromDown(words) + FadeInFromDown(rot_words), + ) + self.wait() + self.add(ghost_line) + self.play( + MoveToTarget(line, path_arc=90 * DEGREES), + Animation(rot_words), + ShowCreation(elbow) ) - self.add_foreground_mobjects(line.copy().set_stroke(LIGHT_GREY, 0.5)) - self.play(MoveToTarget(line, path_arc=90 * DEGREES)) self.wait() self.play( FadeOut(fade_rect), FadeOut(line_dot), - FadeOut(words), - Animation(line) + FadeOut(rot_words), + FadeOut(elbow), + Animation(line), + Animation(ghost_line) ) self.play( LaggedStart(MoveToTarget, lines, run_time=4), Animation(VGroup(e_dot, circle)) ) - self.wait() \ No newline at end of file + self.wait() + self.play( + ShowCreation(ellipse), + Write(ellipse_words, run_time=1) + ) + 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_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 = np.linalg.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 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.scale_to_fit_width(0.3) + + feynman_cut = ImageMobject("FeynmanCut") + feynman_cut.scale_to_fit_height(4) + feynman_cut.next_to(ORIGIN, LEFT) + feynman_cut.shift(UP) + feynman_name = TextMobject("Richard Feynman") + feynman_name.next_to(feynman_cut, DOWN) + feynman_cut.save_state() + feynman_cut.shift(2 * DOWN) + feynman_rect = BackgroundRectangle( + feynman_cut, 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_cut.restore, + MaintainPositionRelativeTo(feynman_rect, feynman_cut), + 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.scale_to_fit_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(LaggedStart( + Write, feynman_diagram, + run_time=4 + )) + self.wait() + self.play( + self.get_book_intro(books[1]), + self.get_book_outro(books[0]), + LaggedStart( + 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.scale_to_fit_height(1) + bongo.set_color(WHITE) + bongo.next_to(safe, RIGHT, LARGE_BUFF) + + objects = VGroup(safe, bongo) + + feynman_smile = ImageMobject("Feynman_smile") + feynman_smile.match_width(objects) + 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(LaggedStart( + 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]), + LaggedStart(FadeOut, joke, run_time=1), + ApplyMethod( + feynman_smile.shift, FRAME_HEIGHT * DOWN, + remover=True + ) + ) + + # As a teacher + feynman_teacher = ImageMobject("Feynman_teacher") + feynman_teacher.scale_to_fit_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, use_rectangular_stem=False) + 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): + def construct(self): + self.add_title() + self.setup_orbits() + + def add_title(self): + pass + + def setup_orbits(self): + pass