diff --git a/active_projects/clacks/all_s2_scenes.py b/active_projects/clacks/all_s2_scenes.py deleted file mode 100644 index 64d949d8..00000000 --- a/active_projects/clacks/all_s2_scenes.py +++ /dev/null @@ -1,28 +0,0 @@ -# from active_projects.clacks import question -# from active_projects.clacks import solution1 -from active_projects.clacks.solution2 import block_collision_scenes -from active_projects.clacks.solution2 import simple_scenes -from active_projects.clacks.solution2 import wordy_scenes -from active_projects.clacks.solution2 import pi_creature_scenes -from active_projects.clacks.solution2 import position_phase_space - -OUTPUT_DIRECTORY = "clacks_solution2" -ALL_SCENE_CLASSES = [ - block_collision_scenes.IntroducePreviousTwoVideos, - block_collision_scenes.PreviousTwoVideos, - wordy_scenes.ConnectionToOptics, - pi_creature_scenes.OnAnsweringTwice, - simple_scenes.LastVideoWrapper, - position_phase_space.IntroducePositionPhaseSpace, - position_phase_space.UnscaledPositionPhaseSpaceMass100, - position_phase_space.EqualMassCase, - pi_creature_scenes.AskAboutEqualMassMomentumTransfer, - position_phase_space.FailedAngleRelation, - position_phase_space.UnscaledPositionPhaseSpaceMass10, - pi_creature_scenes.ComplainAboutRelevanceOfAnalogy, - simple_scenes.LastVideoWrapper, - position_phase_space.RescaleCoordinates, - wordy_scenes.ConnectionToOpticsTransparent, - position_phase_space.RescaleCoordinatesMass16, - position_phase_space.RescaleCoordinatesMass64, -] diff --git a/active_projects/clacks/solution2/simple_scenes.py b/active_projects/clacks/solution2/simple_scenes.py deleted file mode 100644 index 42bfdb7b..00000000 --- a/active_projects/clacks/solution2/simple_scenes.py +++ /dev/null @@ -1,15 +0,0 @@ -from big_ol_pile_of_manim_imports import * - - -class LastVideoWrapper(Scene): - def construct(self): - title = TextMobject("Last time...") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - self.play( - FadeInFromDown(title), - ShowCreation(rect) - ) - self.wait() diff --git a/manimlib/animation/animation.py b/manimlib/animation/animation.py index 58625bca..c0885b74 100644 --- a/manimlib/animation/animation.py +++ b/manimlib/animation/animation.py @@ -29,6 +29,9 @@ class Animation(object): mobject = instantiate(mobject) assert(isinstance(mobject, Mobject)) digest_config(self, kwargs, locals()) + # Make sure it's all up to date + mobject.update() + # Keep track of where it started self.starting_mobject = self.mobject.copy() if self.rate_func is None: self.rate_func = (lambda x: x) diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index dd058c05..3b17140f 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -5,7 +5,6 @@ from manimlib.animation.transform import Transform from manimlib.constants import * from manimlib.mobject.svg.tex_mobject import TextMobject from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.mobject.types.vectorized_mobject import VectorizedPoint from manimlib.utils.bezier import interpolate from manimlib.utils.config_ops import digest_config from manimlib.utils.paths import counterclockwise_path diff --git a/manimlib/animation/rotation.py b/manimlib/animation/rotation.py index 46b9921f..66c1df78 100644 --- a/manimlib/animation/rotation.py +++ b/manimlib/animation/rotation.py @@ -44,7 +44,7 @@ class Rotate(Transform): if "path_arc_axis" not in kwargs: kwargs["path_arc_axis"] = axis digest_config(self, kwargs, locals()) - target = mobject.copy() + target = mobject.deepcopy() if self.in_place: self.about_point = mobject.get_center() target.rotate( diff --git a/manimlib/animation/transform.py b/manimlib/animation/transform.py index 47ec01f5..1dcfeec2 100644 --- a/manimlib/animation/transform.py +++ b/manimlib/animation/transform.py @@ -29,8 +29,10 @@ class Transform(Animation): # Copy target_mobject so as to not mess with caller self.original_target_mobject = target_mobject target_mobject = target_mobject.copy() - mobject.align_data(target_mobject) + target_mobject.update() self.target_mobject = target_mobject + + mobject.align_data(target_mobject) digest_config(self, kwargs) self.init_path_func() diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index ff3500a6..67fa2471 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -30,7 +30,7 @@ class Camera(object): "background_image": None, "pixel_height": DEFAULT_PIXEL_HEIGHT, "pixel_width": DEFAULT_PIXEL_WIDTH, - "frame_duration": DEFAULT_FRAME_DURATION, + "frame_rate": DEFAULT_FRAME_RATE, # Note: frame height and width will be resized to match # the pixel aspect ratio "frame_height": FRAME_HEIGHT, diff --git a/manimlib/constants.py b/manimlib/constants.py index b34da3b5..20fae7fd 100644 --- a/manimlib/constants.py +++ b/manimlib/constants.py @@ -109,7 +109,7 @@ LOW_QUALITY_CAMERA_CONFIG = { DEFAULT_PIXEL_HEIGHT = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_height"] DEFAULT_PIXEL_WIDTH = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"] -DEFAULT_FRAME_DURATION = 30 +DEFAULT_FRAME_RATE = 60 DEFAULT_POINT_DENSITY_2D = 25 DEFAULT_POINT_DENSITY_1D = 250 diff --git a/manimlib/for_3b1b_videos/common_scenes.py b/manimlib/for_3b1b_videos/common_scenes.py index 4eb1241e..6b686906 100644 --- a/manimlib/for_3b1b_videos/common_scenes.py +++ b/manimlib/for_3b1b_videos/common_scenes.py @@ -307,6 +307,7 @@ class Banner(Scene): "date": "Sunday, February 3rd", "message_scale_val": 0.9, "add_supporter_note": False, + "pre_date_text": "Next video on ", } def __init__(self, **kwargs): @@ -363,7 +364,8 @@ class Banner(Scene): def get_date_message(self): return TextMobject( - "Next video on ", self.date, + self.pre_date_text, + self.date, tex_to_color_map={self.date: YELLOW}, ) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 093118d9..08d895d3 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -403,10 +403,11 @@ class Line(VMobject): start, end = self.get_start_and_end() return angle_of_vector(end - start) - # def put_start_and_end_on(self, new_start, new_end): - # self.set_start_and_end(new_start, new_end) - # self.buff = 0 - # self.generate_points() + def set_angle(self, angle): + self.rotate( + angle - self.get_angle(), + about_point=self.get_start(), + ) def put_start_and_end_on(self, new_start, new_end): self.start = new_start diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 8224c2b2..283affbc 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -125,6 +125,7 @@ class Mobject(Container): copy_mobject.submobjects = [ submob.copy() for submob in self.submobjects ] + copy_mobject.updaters = list(self.updaters) family = self.get_family() for attr, value in list(self.__dict__.items()): if isinstance(value, Mobject) and value in family and value is not self: @@ -147,13 +148,14 @@ class Mobject(Container): # Updating def update(self, dt=0, recursive=True): - if not self.updating_suspended: - for updater in self.updaters: - parameters = get_parameters(updater) - if "dt" in parameters: - updater(self, dt) - else: - updater(self) + if self.updating_suspended: + return self + for updater in self.updaters: + parameters = get_parameters(updater) + if "dt" in parameters: + updater(self, dt) + else: + updater(self) if recursive: for submob in self.submobjects: submob.update(dt, recursive) @@ -182,8 +184,11 @@ class Mobject(Container): self.updaters.remove(update_function) return self - def clear_updaters(self): + def clear_updaters(self, recursive=True): self.updaters = [] + if recursive: + for submob in self.submobjects: + submob.clear_updaters() return self def suspend_updating(self, recursive=True): diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 43ba2319..c0603356 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -32,9 +32,9 @@ class VMobject(Mobject): "background_stroke_width": 0, # When a color c is set, there will be a second color # computed based on interpolating c to WHITE by with - # sheen, and the display will gradient to this + # sheen_factor, and the display will gradient to this # secondary color in the direction of sheen_direction. - "sheen": 0.0, + "sheen_factor": 0.0, "sheen_direction": UL, # Indicates that it will not be displayed, but # that it should count in parent mobject's path @@ -71,7 +71,7 @@ class VMobject(Mobject): family=self.propagate_style_to_family, ) self.set_sheen( - factor=self.sheen, + factor=self.sheen_factor, direction=self.sheen_direction, family=self.propagate_style_to_family ) @@ -81,7 +81,7 @@ class VMobject(Mobject): """ First arg can be either a color, or a tuple/list of colors. Likewise, opacity can either be a float, or a tuple of floats. - If self.sheen is not zero, and only + If self.sheen_factor is not zero, and only one color was passed in, a second slightly light color will automatically be added for the gradient """ @@ -92,10 +92,10 @@ class VMobject(Mobject): for c, o in zip(*make_even(colors, opacities)) ]) - sheen = self.get_sheen() - if sheen != 0 and len(rgbas) == 1: + sheen_factor = self.get_sheen_factor() + if sheen_factor != 0 and len(rgbas) == 1: light_rgbas = np.array(rgbas) - light_rgbas[:, :3] += sheen + light_rgbas[:, :3] += sheen_factor clip_in_place(light_rgbas, 0, 1) rgbas = np.append(rgbas, light_rgbas, axis=0) return rgbas @@ -160,8 +160,10 @@ class VMobject(Mobject): fill_opacity=None, stroke_color=None, stroke_width=None, + stroke_opacity=None, background_stroke_color=None, background_stroke_width=None, + background_stroke_opacity=None, sheen_factor=None, sheen_direction=None, background_image_file=None, @@ -174,11 +176,13 @@ class VMobject(Mobject): self.set_stroke( color=stroke_color, width=stroke_width, + opacity=stroke_opacity, family=family, ) self.set_background_stroke( color=background_stroke_color, width=background_stroke_width, + opacity=background_stroke_opacity, family=family, ) if sheen_factor: @@ -198,7 +202,7 @@ class VMobject(Mobject): "stroke_width": self.get_stroke_width(), "background_stroke_color": self.get_stroke_colors(background=True), "background_stroke_width": self.get_stroke_width(background=True), - "sheen_factor": self.get_sheen(), + "sheen_factor": self.get_sheen_factor(), "sheen_direction": self.get_sheen_direction(), "background_image_file": self.get_background_image_file(), } @@ -309,12 +313,12 @@ class VMobject(Mobject): if family: for submob in self.submobjects: submob.set_sheen(factor, direction, family) - self.sheen = factor + self.sheen_factor = factor if direction is not None: # family set to false because recursion will # already be handled above self.set_sheen_direction(direction, family=False) - # Reset color to put sheen into effect + # Reset color to put sheen_factor into effect if factor != 0: self.set_stroke(self.get_stroke_color(), family=family) self.set_fill(self.get_fill_color(), family=family) @@ -323,8 +327,8 @@ class VMobject(Mobject): def get_sheen_direction(self): return np.array(self.sheen_direction) - def get_sheen(self): - return self.sheen + def get_sheen_factor(self): + return self.sheen_factor def get_gradient_start_and_end_points(self): if self.shade_in_3d: @@ -613,7 +617,7 @@ class VMobject(Mobject): "stroke_width", "background_stroke_width", "sheen_direction", - "sheen", + "sheen_factor", ] for attr in attrs: setattr(self, attr, interpolate( @@ -694,7 +698,7 @@ class VectorizedPoint(VMobject): return self.artificial_height def get_location(self): - return self.points[0] + return np.array(self.points[0]) def set_location(self, new_loc): self.set_points(np.array([new_loc])) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 1ec7e674..200d7303 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -528,6 +528,7 @@ class Scene(Container): @handle_play_like_call def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): dt = 1 / self.camera.frame_rate + self.continual_update(dt=0) # Any problems with this? if self.should_continually_update(): time_progression = self.get_wait_time_progression(duration, stop_condition) for t in time_progression: diff --git a/old_projects/clacks/all_s2_scenes.py b/old_projects/clacks/all_s2_scenes.py new file mode 100644 index 00000000..1038fd05 --- /dev/null +++ b/old_projects/clacks/all_s2_scenes.py @@ -0,0 +1,62 @@ +from old_projects.clacks import question +from old_projects.clacks.solution2 import block_collision_scenes +from old_projects.clacks.solution2 import mirror_scenes +from old_projects.clacks.solution2 import pi_creature_scenes +from old_projects.clacks.solution2 import position_phase_space +from old_projects.clacks.solution2 import simple_scenes +from old_projects.clacks.solution2 import wordy_scenes + +OUTPUT_DIRECTORY = "clacks_solution2" +ALL_SCENE_CLASSES = [ + question.NameIntro, + block_collision_scenes.IntroducePreviousTwoVideos, + block_collision_scenes.PreviousTwoVideos, + simple_scenes.ComingUpWrapper, + wordy_scenes.ConnectionToOptics, + pi_creature_scenes.OnAnsweringTwice, + simple_scenes.LastVideoWrapper, + simple_scenes.Rectangle, + simple_scenes.ShowRectangleCreation, + simple_scenes.LeftEdge, + simple_scenes.RightEdge, + position_phase_space.IntroducePositionPhaseSpace, + position_phase_space.UnscaledPositionPhaseSpaceMass100, + simple_scenes.FourtyFiveDegreeLine, + position_phase_space.EqualMassCase, + pi_creature_scenes.AskAboutEqualMassMomentumTransfer, + position_phase_space.FailedAngleRelation, + position_phase_space.UnscaledPositionPhaseSpaceMass10, + pi_creature_scenes.ComplainAboutRelevanceOfAnalogy, + simple_scenes.LastVideoWrapper, + simple_scenes.NoteOnEnergyLostToSound, + position_phase_space.RescaleCoordinates, + wordy_scenes.ConnectionToOpticsTransparent, + position_phase_space.RescaleCoordinatesMass16, + position_phase_space.RescaleCoordinatesMass64, + position_phase_space.RescaleCoordinatesMass100, + position_phase_space.IntroduceVelocityVector, + position_phase_space.IntroduceVelocityVectorWithoutZoom, + position_phase_space.ShowMomentumConservation, + wordy_scenes.RearrangeMomentumEquation, + simple_scenes.DotProductVideoWrapper, + simple_scenes.ShowDotProductMeaning, + position_phase_space.JustTheProcessNew, + mirror_scenes.ShowTrajectoryWithChangingTheta, + pi_creature_scenes.ReplaceOneTrickySceneWithAnother, + mirror_scenes.MirrorAndWiresOverlay, + pi_creature_scenes.NowForTheGoodPart, + mirror_scenes.ReflectWorldThroughMirrorNew, + mirror_scenes.ReflectWorldThroughMirrorThetaPoint2, + mirror_scenes.ReflectWorldThroughMirrorThetaPoint1, + simple_scenes.AskAboutAddingThetaToItself, + simple_scenes.AskAboutAddingThetaToItselfThetaPoint1, + simple_scenes.AskAboutAddingThetaToItselfThetaPoint2, + simple_scenes.FinalFormula, + simple_scenes.ArctanSqrtPoint1Angle, + simple_scenes.ReviewWrapper, + simple_scenes.SurprisedRandy, + simple_scenes.TwoSolutionsWrapper, + simple_scenes.FinalQuote, + simple_scenes.EndScreen, + simple_scenes.ClacksSolution2Thumbnail, +] diff --git a/active_projects/clacks/name_bump.py b/old_projects/clacks/name_bump.py similarity index 97% rename from active_projects/clacks/name_bump.py rename to old_projects/clacks/name_bump.py index cd79392d..24a09813 100644 --- a/active_projects/clacks/name_bump.py +++ b/old_projects/clacks/name_bump.py @@ -1,7 +1,6 @@ #!/usr/bin/env python from big_ol_pile_of_manim_imports import * - -from active_projects.clacks.question import BlocksAndWallExample +from old_projects.clacks.question import BlocksAndWallExample class NameBump(BlocksAndWallExample): diff --git a/active_projects/clacks/question.py b/old_projects/clacks/question.py similarity index 98% rename from active_projects/clacks/question.py rename to old_projects/clacks/question.py index ca8642a2..f9b8ca98 100644 --- a/active_projects/clacks/question.py +++ b/old_projects/clacks/question.py @@ -13,7 +13,7 @@ class Block(Square): "stroke_color": WHITE, "fill_color": None, "sheen_direction": UL, - "sheen": 0.5, + "sheen_factor": 0.5, "sheen_direction": UL, } @@ -43,7 +43,7 @@ class Block(Square): def mass_to_color(self, mass): colors = [ LIGHT_GREY, - BLUE_B, + BLUE_D, BLUE_D, BLUE_E, BLUE_E, @@ -225,7 +225,7 @@ class ClackFlashes(ContinualAnimation): ContinualAnimation.__init__(self, group, **kwargs) def update_mobject(self, dt): - total_time = self.external_time + total_time = self.get_time() group = self.mobject for flash in self.flashes: if flash.start_time < total_time < flash.end_time: @@ -238,6 +238,9 @@ class ClackFlashes(ContinualAnimation): if flash.mobject in group: group.remove(flash.mobject) + def get_time(self): + return self.external_time + class Wall(Line): CONFIG = { @@ -410,6 +413,10 @@ class NameIntro(Scene): rate_func=None, ) ) + self.play( + Flash(brown.get_right(), run_time=flash_time), + Restore(brown, rate_func=None) + ) class MathAndPhysicsConspiring(Scene): @@ -1558,16 +1565,16 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene): BlocksAndWallExample.setup(self) def construct(self): - # self.camera_frame.shift(0.9 * UP) - self.mobjects.insert( - 0, - FullScreenFadeRectangle( - color=DARK_GREY, - opacity=0.5, - sheen_direction=UL, - sheen=0.5, - ), - ) + self.camera_frame.shift(0.9 * UP) + # self.mobjects.insert( + # 0, + # FullScreenFadeRectangle( + # color=DARK_GREY, + # opacity=0.5, + # sheen_direction=UL, + # sheen=0.5, + # ), + # ) self.thicken_lines() self.grow_labels() self.add_vector() @@ -1587,7 +1594,7 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene): def add_vector(self): blocks = self.blocks - arrow = Vector( + arrow = self.arrow = Vector( 2.5 * LEFT, color=RED, rectangular_stem_width=1.5, @@ -1600,7 +1607,9 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene): self.add(arrow) def add_text(self): - question = TextMobject("How many\\\\collisions?") + question = self.question = TextMobject( + "How many\\\\collisions?" + ) question.scale(2.5) question.to_edge(UP) question.set_color(YELLOW) diff --git a/active_projects/clacks/solution1.py b/old_projects/clacks/solution1.py similarity index 99% rename from active_projects/clacks/solution1.py rename to old_projects/clacks/solution1.py index fdd139b2..b072d580 100644 --- a/active_projects/clacks/solution1.py +++ b/old_projects/clacks/solution1.py @@ -1,5 +1,5 @@ from big_ol_pile_of_manim_imports import * -from active_projects.clacks.question import * +from old_projects.clacks.question import * from old_projects.div_curl import ShowTwoPopulations diff --git a/active_projects/clacks/solution2/block_collision_scenes.py b/old_projects/clacks/solution2/block_collision_scenes.py similarity index 95% rename from active_projects/clacks/solution2/block_collision_scenes.py rename to old_projects/clacks/solution2/block_collision_scenes.py index b300087f..e965e5f9 100644 --- a/active_projects/clacks/solution2/block_collision_scenes.py +++ b/old_projects/clacks/solution2/block_collision_scenes.py @@ -1,5 +1,5 @@ from big_ol_pile_of_manim_imports import * -from active_projects.clacks.question import BlocksAndWallExample +from old_projects.clacks.question import BlocksAndWallExample class PreviousTwoVideos(BlocksAndWallExample): @@ -7,7 +7,7 @@ class PreviousTwoVideos(BlocksAndWallExample): "sliding_blocks_config": { "block1_config": { "mass": 1e2, - "velocity": -1, + "velocity": -2, "width": 4, "distance": 8, }, diff --git a/old_projects/clacks/solution2/mirror_scenes.py b/old_projects/clacks/solution2/mirror_scenes.py new file mode 100644 index 00000000..7999e65a --- /dev/null +++ b/old_projects/clacks/solution2/mirror_scenes.py @@ -0,0 +1,1021 @@ +from big_ol_pile_of_manim_imports import * + + +class MirrorScene(Scene): + CONFIG = { + "center": DOWN + 3 * LEFT, + "line_length": FRAME_WIDTH, + "start_theta": np.arctan(0.25), + "start_y_offset": 0.5, + "start_x_offset": 8, + "arc_config": { + "radius": 1, + "stroke_color": WHITE, + "stroke_width": 2, + }, + "trajectory_point_spacing": 0.1, + "trajectory_style": { + "stroke_color": YELLOW, + "stroke_width": 2, + }, + "ghost_lines_style": { + "stroke_color": WHITE, + "stroke_width": 1, + "stroke_opacity": 0.5, + }, + # "reflect_sound": "ping", + "reflect_sound": "pen_click", + } + + def setup(self): + self.theta_tracker = ValueTracker(self.start_theta) + self.start_y_offset_tracker = ValueTracker(self.start_y_offset) + self.start_x_offset_tracker = ValueTracker(self.start_x_offset) + self.center_tracker = VectorizedPoint(self.center) + self.beam_point = VectorizedPoint(np.array([ + self.get_start_x_offset(), + self.get_start_y_offset(), + 0 + ])) + self.ghost_beam_point = self.beam_point.copy() + self.is_sound_allowed = False + + self.mirrors = self.get_mirrors() + self.arc = self.get_arc() + self.theta_symbol = self.get_theta_symbol() + self.trajectory = self.get_trajectory() + self.ghost_trajectory = self.get_ghost_trajectory() + self.theta_display = self.get_theta_display() + self.count_display_word = self.get_count_display_word() + self.count_display_number = self.get_count_display_number() + self.last_count = self.get_count() + + # Add some of them + self.add( + self.mirrors, + self.arc, + self.theta_symbol, + self.theta_display, + self.count_display_word, + self.count_display_number, + ) + + def get_center(self): + return self.center_tracker.get_location() + + def get_theta(self): + return self.theta_tracker.get_value() + + def get_start_y_offset(self): + return self.start_y_offset_tracker.get_value() + + def get_start_x_offset(self): + return self.start_x_offset_tracker.get_value() + + def get_mirror(self): + mirror = VGroup( + Line(ORIGIN, 2 * RIGHT), + Line(ORIGIN, 2 * RIGHT), + Line(ORIGIN, (self.line_length - 4) * RIGHT), + ) + mirror.arrange_submobjects(RIGHT, buff=0) + mirror.set_stroke(width=5) + mirror[0::2].set_stroke((WHITE, GREY)) + mirror[1::2].set_stroke((GREY, WHITE)) + return mirror + + def get_mirrors(self): + mirrors = VGroup(self.get_mirror(), self.get_mirror()) + + def update_mirrors(mirrors): + m1, m2 = mirrors + center = self.get_center() + theta = self.get_theta() + m1.move_to(center, DL) + m2.become(m1) + m2.rotate(theta, about_point=center) + + mirrors.add_updater(update_mirrors) + return mirrors + + def get_arc(self, radius=0.5): + return updating_mobject_from_func(lambda: Arc( + start_angle=0, + angle=self.get_theta(), + arc_center=self.get_center(), + **self.arc_config, + )) + + def get_theta_symbol(self, arc=None, buff=0.15): + if arc is None: + arc = self.arc + symbol = TexMobject("\\theta") + + def update_symbol(symbol): + midpoint = arc.point_from_proportion(0.5) + center = arc.arc_center + vect = (midpoint - center) + max_height = 0.8 * arc.get_height() + if symbol.get_height() > max_height: + symbol.set_height(max_height) + symbol.move_to( + center + vect + buff * normalize(vect) + ) + symbol.add_updater(update_symbol) + return symbol + + def get_ghost_collision_points(self): + x = self.get_start_x_offset() + y = self.get_start_y_offset() + theta = self.get_theta() + + points = [np.array([x, y, 0])] + points += [ + np.array([x, y, 0]) + for k in range(1, int(PI / theta) + 1) + for x in [y / np.tan(k * theta)] + if abs(x) < FRAME_WIDTH + ] + points.append(points[-1] + x * LEFT) + points = np.array(points) + points += self.get_center() + return points + + def get_collision_points(self, ghost_points=None): + if ghost_points is None: + ghost_points = self.get_ghost_collision_points() + theta = self.get_theta() + center = self.get_center() + points = [] + for ghost_point in ghost_points: + vect = ghost_point - center + angle = angle_of_vector(vect) + k = int(angle / theta) + if k % 2 == 0: + vect = rotate_vector(vect, -k * theta) + else: + vect = rotate_vector(vect, -(k + 1) * theta) + vect[1] = abs(vect[1]) + points.append(center + vect) + return points + + def get_trajectory(self, collision_points=None): + if collision_points is None: + collision_points = self.get_collision_points() + points = [] + spacing = self.trajectory_point_spacing + for p0, p1 in zip(collision_points, collision_points[1:]): + n_intervals = max(1, int(get_norm(p1 - p0) / spacing)) + for alpha in np.linspace(0, 1, n_intervals + 1): + points.append(interpolate(p0, p1, alpha)) + trajectory = VMobject() + trajectory.set_points_as_corners(points) + trajectory.set_style(**self.trajectory_style) + return trajectory + + def get_ghost_trajectory(self): + return self.get_trajectory(self.get_ghost_collision_points()) + + def get_collision_point_counts(self, collision_points=None): + if collision_points is None: + collision_points = self.get_collision_points()[1:-1] + result = VGroup() + for n, point in enumerate(collision_points): + count = Integer(n + 1) + count.set_height(0.25) + vect = UP if n % 2 == 0 else DOWN + count.next_to(point, vect, SMALL_BUFF) + result.add(count) + return result + + def get_collision_count_anim(self, collision_point_counts=None): + if collision_point_counts is None: + collision_point_counts = self.get_collision_point_counts() + group = VGroup() + + def update(group): + count = self.get_count() + if count == 0: + group.submobjects = [] + elif count < len(collision_point_counts) + 1: + group.submobjects = [ + collision_point_counts[count - 1] + ] + + return UpdateFromFunc(group, update, remover=True) + + def get_ghost_lines(self): + line = self.mirrors[0] + center = self.get_center() + theta = self.get_theta() + lines = VGroup() + for k in range(1, int(PI / theta) + 2): + new_line = line.copy() + new_line.rotate(k * theta, about_point=center) + lines.add(new_line) + lines.set_style(**self.ghost_lines_style) + return lines + + # Displays + def get_theta_display(self): + lhs = TexMobject("\\theta = ") + radians = DecimalNumber() + radians.add_updater( + lambda m: m.set_value(self.get_theta()) + ) + radians_word = TextMobject("radians") + radians_word.next_to( + radians, RIGHT, aligned_edge=DOWN + ) + equals = TexMobject("=") + degrees = Integer(0, unit="^\\circ") + degrees.add_updater( + lambda m: m.set_value( + int(np.round(self.get_theta() / DEGREES)) + ) + ) + group = VGroup(lhs, radians, radians_word, equals, degrees) + group.arrange_submobjects(RIGHT, aligned_edge=DOWN) + equals.align_to(lhs[-1], DOWN) + group.to_corner(UL) + return group + + def get_count_display_word(self): + result = TextMobject("\\# Bounces: ") + result.to_corner(UL) + result.shift(DOWN) + result.set_color(YELLOW) + return result + + def get_count_display_number(self, count_display_word=None, ghost_beam_point=None): + if count_display_word is None: + count_display_word = self.count_display_word + result = Integer() + result.next_to( + count_display_word[-1], RIGHT, + aligned_edge=DOWN, + ) + result.set_color(YELLOW) + result.add_updater( + lambda m: m.set_value(self.get_count()) + ) + return result + + def get_count(self, ghost_beam_point=None): + if ghost_beam_point is None: + ghost_beam_point = self.ghost_beam_point.get_location() + angle = angle_of_vector( + ghost_beam_point - self.get_center() + ) + return int(angle / self.get_theta()) + + # Sounds + def allow_sound(self): + self.is_sound_allowed = True + + def disallow_sound(self): + self.is_sound_allowed = False + + def continual_update(self, dt): + super().continual_update(dt) + if self.get_count() != self.last_count: + self.last_count = self.get_count() + if self.is_sound_allowed: + self.add_sound( + self.reflect_sound, + gain=-20, + ) + + # Bouncing animations + def show_bouncing(self, run_time=5): + trajectory = self.trajectory + ghost_trajectory = self.get_ghost_trajectory() + + beam_anims = self.get_shooting_beam_anims( + trajectory, ghost_trajectory + ) + count_anim = self.get_collision_count_anim() + + self.allow_sound() + self.play(count_anim, *beam_anims, run_time=run_time) + self.disallow_sound() + + def get_special_flash(self, mobject, stroke_width, time_width, rate_func=None, **kwargs): + kwargs["rate_func"] = rate_func + mob_copy = mobject.copy() + mob_copy.set_stroke(width=stroke_width) + mob_copy.time_width = time_width + return UpdateFromAlphaFunc( + mob_copy, + lambda m, a: m.pointwise_become_partial( + mobject, + max(a - (1 - a) * m.time_width, 0), + a, + ), + **kwargs + ) + + def get_shooting_beam_anims(self, + trajectory, + ghost_trajectory=None, + update_beam_point=True, + num_flashes=20, + min_time_width=0.01, + max_time_width=0.5, + min_stroke_width=0.01, + max_stroke_width=6, + fade_trajectory=True, + faded_trajectory_width=0.25, + faded_trajectory_time_exp=0.2, + ): + # Most flashes + result = [ + self.get_special_flash(trajectory, stroke_width, time_width) + for stroke_width, time_width in zip( + np.linspace(max_stroke_width, min_stroke_width, num_flashes), + np.linspace(min_time_width, max_time_width, num_flashes), + ) + ] + + # Make sure beam point is updated + if update_beam_point: + smallest_flash = result[0] + result.append( + UpdateFromFunc( + self.beam_point, + lambda m: m.move_to(smallest_flash.mobject.points[-1]) + ) + ) + + # Make sure ghost beam point is updated + if ghost_trajectory: + ghost_flash = self.get_special_flash( + ghost_trajectory, 0, min_time_width, + ) + ghost_beam_point_update = UpdateFromFunc( + self.ghost_beam_point, + lambda m: m.move_to(ghost_flash.mobject.points[-1]) + ) + result += [ + ghost_flash, + ghost_beam_point_update, + ] + + # Fade trajectory + if fade_trajectory: + ftte = faded_trajectory_time_exp + result.append( + ApplyMethod( + trajectory.set_stroke, + {"width": faded_trajectory_width}, + rate_func=lambda t: there_and_back(t)**ftte + ), + ) + return result + + +class ShowTrajectoryWithChangingTheta(MirrorScene): + def construct(self): + trajectory = self.trajectory + self.add(trajectory) + angles = [30 * DEGREES, 10 * DEGREES] + ys = [1, 1] + self.show_bouncing() + for angle, y in zip(angles, ys): + rect = SurroundingRectangle(self.theta_display) + self.play( + self.theta_tracker.set_value, angle, + self.start_y_offset_tracker.set_value, y, + FadeIn(rect, rate_func=there_and_back, remover=True), + UpdateFromFunc( + trajectory, + lambda m: m.become(self.get_trajectory()) + ), + run_time=2 + ) + self.show_bouncing() + self.wait(2) + + +class ReflectWorldThroughMirrorNew(MirrorScene): + CONFIG = { + "start_y_offset": 1.25, + "center": DOWN, + "randy_height": 1, + "partial_trajectory_values": [ + 0, 0.22, 0.28, 0.315, 1, + ], + } + + def construct(self): + self.add_randy() + self.shift_displays() + self.add_ghost_beam_point() + self.up_through_first_bounce() + self.create_reflected_worlds() + self.create_reflected_trajectories() + self.first_reflection() + self.next_reflection(2) + self.next_reflection(3) + self.unfold_all_reflected_worlds() + self.show_completed_beam() + self.blink_all_randys() + self.add_randy_updates() + self.show_all_trajectories() + self.focus_on_two_important_trajectories() + + def add_randy(self): + randy = self.randy = Randolph() + randy.flip() + randy.set_height(self.randy_height) + randy.change("pondering") + randy.align_to(self.mirrors, DOWN) + randy.shift(0.01 * UP) + randy.to_edge(RIGHT, buff=1) + randy.tracked_mobject = self.trajectory + randy.add_updater( + lambda m: m.look_at( + m.tracked_mobject.points[-1] + ) + ) + self.add(randy) + + def shift_displays(self): + VGroup( + self.theta_display, + self.count_display_word, + self.count_display_number, + ).to_edge(DOWN) + + def add_ghost_beam_point(self): + self.ghost_beam_point.add_updater( + lambda m: m.move_to( + self.ghost_trajectory.points[-1] + ) + ) + self.add(self.ghost_beam_point) + + def up_through_first_bounce(self): + self.play(*self.get_both_partial_trajectory_anims( + *self.partial_trajectory_values[:2] + )) + self.wait() + + def create_reflected_worlds(self): + mirrors = self.mirrors + triangle = Polygon(*[ + mirrors.get_corner(corner) + for corner in (DR, DL, UR) + ]) + triangle.set_stroke(width=0) + triangle.set_fill(BLUE_E, opacity=0) + world = self.world = VGroup( + triangle, + mirrors, + self.arc, + self.theta_symbol, + self.randy, + ) + reflected_worlds = self.get_reflected_worlds(world) + self.reflected_worlds = reflected_worlds + # Alternating triangle opacities + for rw in reflected_worlds[::2]: + rw[0].set_fill(opacity=0.25) + + def create_reflected_trajectories(self): + self.reflected_trajectories = updating_mobject_from_func( + lambda: self.get_reflected_worlds(self.trajectory) + ) + + def first_reflection(self): + reflected_trajectory = self.reflected_trajectories[0] + reflected_world = self.reflected_worlds[0] + world = self.world + trajectory = self.trajectory + ghost_trajectory = self.ghost_trajectory + + self.play( + TransformFromCopy(world, reflected_world), + TransformFromCopy(trajectory, reflected_trajectory), + run_time=2 + ) + beam_anims = self.get_shooting_beam_anims( + ghost_trajectory, + fade_trajectory=False, + ) + self.play( + *[ + ApplyMethod(m.set_stroke, GREY, 1) + for m in (trajectory, reflected_trajectory) + ] + beam_anims, + run_time=2 + ) + for x in range(2): + self.play(*beam_anims, run_time=2) + + ghost_trajectory.set_stroke(YELLOW, 4) + self.bring_to_front(ghost_trajectory) + self.play(FadeIn(ghost_trajectory)) + self.wait() + + def next_reflection(self, index=2): + i = index + self.play( + *self.get_both_partial_trajectory_anims( + *self.partial_trajectory_values[i - 1:i + 1] + ), + UpdateFromFunc( + VMobject(), # Null + lambda m: self.reflected_trajectories.update(), + remover=True, + ), + ) + + anims = [ + TransformFromCopy(*reflections[i - 2:i]) + for reflections in [ + self.reflected_worlds, + self.reflected_trajectories + ] + ] + self.play(*anims, run_time=2) + self.add(self.ghost_trajectory) + self.wait() + + def unfold_all_reflected_worlds(self): + worlds = self.reflected_worlds + trajectories = self.reflected_trajectories + + pairs = [ + (VGroup(w1, t1), VGroup(w2, t2)) + for w1, w2, t1, t2 in zip( + worlds[2:], worlds[3:], + trajectories[2:], trajectories[3:], + ) + ] + + new_worlds = VGroup() # Brought to you by Dvorak + for m1, m2 in pairs: + m2.pre_world = m1.copy() + new_worlds.add(m2) + for mob in new_worlds: + mob.save_state() + mob.become(mob.pre_world) + mob.fade(1) + + self.play(LaggedStart( + Restore, new_worlds, + lag_ratio=0.4, + run_time=3 + )) + + def show_completed_beam(self): + self.add(self.reflected_trajectories) + self.add(self.ghost_trajectory) + self.play(*self.get_both_partial_trajectory_anims( + *self.partial_trajectory_values[-2:], + run_time=7 + )) + + def blink_all_randys(self): + randys = self.randys = VGroup(self.randy) + randys.add(*[rw[-1] for rw in self.reflected_worlds]) + self.play(LaggedStart(Blink, randys)) + + def add_randy_updates(self): + # Makes it run slower, but it's fun! + reflected_randys = VGroup(*[ + rw[-1] for rw in self.reflected_worlds + ]) + reflected_randys.add_updater( + lambda m: m.become( + self.get_reflected_worlds(self.randy) + ) + ) + self.add(reflected_randys) + + def show_all_trajectories(self): + ghost_trajectory = self.ghost_trajectory + reflected_trajectories = self.reflected_trajectories + trajectory = self.trajectory + reflected_trajectories.suspend_updating() + trajectories = VGroup(trajectory, *reflected_trajectories) + + all_mirrors = VGroup(*[ + world[1] + for world in it.chain([self.world], self.reflected_worlds) + ]) + + self.play( + FadeOut(ghost_trajectory), + trajectories.set_stroke, YELLOW, 0.5, + all_mirrors.set_stroke, {"width": 1}, + ) + + # All trajectory light beams + flash_groups = [ + self.get_shooting_beam_anims( + mob, fade_trajectory=False, + ) + for mob in trajectories + ] + all_flashes = list(it.chain(*flash_groups)) + + # Have all the pi creature eyes follows + self.randy.tracked_mobject = all_flashes[0].mobject + + # Highlight the illustory straight beam + red_ghost = self.ghost_trajectory.copy() + red_ghost.set_color(RED) + red_ghost_beam = self.get_shooting_beam_anims( + red_ghost, fade_trajectory=False, + ) + + num_repeats = 3 + for x in range(num_repeats): + anims = list(all_flashes) + if x == num_repeats - 1: + anims += list(red_ghost_beam) + self.randy.tracked_mobject = red_ghost_beam[0].mobject + for flash in all_flashes: + if hasattr(flash.mobject, "time_width"): + flash.mobject.set_stroke( + width=0.25 * flash.mobject.get_stroke_width() + ) + flash.mobject.time_width *= 0.25 + self.play(*anims, run_time=3) + + def focus_on_two_important_trajectories(self): + self.ghost_trajectory.set_stroke(YELLOW, 1) + self.play( + FadeOut(self.reflected_trajectories), + FadeIn(self.ghost_trajectory), + self.trajectory.set_stroke, YELLOW, 1, + ) + self.add_flashing_windows() + t_beam_anims = self.get_shooting_beam_anims(self.trajectory) + gt_beam_anims = self.get_shooting_beam_anims(self.ghost_trajectory) + self.ghost_beam_point.clear_updaters() + self.ghost_beam_point.add_updater( + lambda m: m.move_to( + gt_beam_anims[0].mobject.points[-1] + ) + ) + self.randy.tracked_mobject = t_beam_anims[0].mobject + self.allow_sound() + self.play( + *t_beam_anims, *gt_beam_anims, + run_time=6 + ) + self.add_room_color_updates() + self.play( + *t_beam_anims, *gt_beam_anims, + run_time=6 + ) + self.blink_all_randys() + self.play( + *t_beam_anims, *gt_beam_anims, + run_time=6 + ) + + # Helpers + def get_reflected_worlds(self, world, n_reflections=None): + theta = self.get_theta() + center = self.get_center() + if n_reflections is None: + n_reflections = int(PI / theta) + + result = VGroup() + last_world = world + for n in range(n_reflections): + vect = rotate_vector(RIGHT, (n + 1) * theta) + reflected_world = last_world.copy() + reflected_world.clear_updaters() + reflected_world.rotate( + PI, axis=vect, about_point=center, + ) + last_world = reflected_world + result.add(last_world) + return result + + def get_partial_trajectory_anims(self, trajectory, a, b, **kwargs): + if not hasattr(trajectory, "full_self"): + trajectory.full_self = trajectory.copy() + return UpdateFromAlphaFunc( + trajectory, + lambda m, alpha: m.pointwise_become_partial( + m.full_self, 0, + interpolate(a, b, alpha) + ), + **kwargs + ) + + def get_both_partial_trajectory_anims(self, a, b, run_time=2, **kwargs): + kwargs["run_time"] = run_time + return [ + self.get_partial_trajectory_anims( + mob, a, b, **kwargs + ) + for mob in (self.trajectory, self.ghost_trajectory) + ] + + def add_flashing_windows(self): + theta = self.get_theta() + center = self.get_center() + windows = self.windows = VGroup(*[ + Line( + center, + center + rotate_vector(10 * RIGHT, k * theta), + color=BLUE, + stroke_width=0, + ) + for k in range(0, self.get_count() + 1) + ]) + windows[0].set_stroke(opacity=0) + + # Warning, windows update manager may launch + def update_windows(windows): + windows.set_stroke(width=0) + windows[self.get_count()].set_stroke(width=5) + windows.add_updater(update_windows) + self.add(windows) + + def add_room_color_updates(self): + def update_reflected_worlds(worlds): + for n, world in enumerate(worlds): + worlds[n][0].set_fill( + opacity=(0.25 if (n % 2 == 0) else 0) + ) + index = self.get_count() - 1 + if index < 0: + return + worlds[index][0].set_fill(opacity=0.5) + self.reflected_worlds.add_updater(update_reflected_worlds) + self.add(self.reflected_worlds) + + +class ReflectWorldThroughMirrorThetaPoint2(ReflectWorldThroughMirrorNew): + CONFIG = { + "start_theta": 0.2, + "randy_height": 0.8, + } + + +class ReflectWorldThroughMirrorThetaPoint1(ReflectWorldThroughMirrorNew): + CONFIG = { + "start_theta": 0.1, + "randy_height": 0.5, + "start_y_offset": 0.5, + "arc_config": { + "radius": 0.5, + }, + } + + +class MirrorAndWiresOverlay(MirrorScene): + CONFIG = { + "wire_pixel_points": [ + (355, 574), + (846, 438), + (839, 629), + (845, 288), + (1273, 314), + ], + "max_x_pixel": 1440, + "max_y_pixel": 1440, + } + + def setup(self): + self.last_count = 0 + + def get_count(self): + return 0 + + def get_shooting_beam_anims(self, mobject, **new_kwargs): + kwargs = { + "update_beam_point": False, + "fade_trajectory": False, + "max_stroke_width": 10, + } + kwargs.update(new_kwargs) + return super().get_shooting_beam_anims(mobject, **kwargs) + + def construct(self): + self.add_wires() + self.add_diagram() + + self.introduce_wires() + self.show_illusion() + self.show_angles() + + # self.show_reflecting_beam() + + def add_wires(self): + ul_corner = TOP + LEFT_SIDE + points = self.wire_points = [ + ul_corner + np.array([ + (x / self.max_x_pixel) * FRAME_HEIGHT, + (-y / self.max_y_pixel) * FRAME_HEIGHT, + 0 + ]) + for x, y in self.wire_pixel_points + ] + wires = self.wires = VGroup( + Line(points[0], points[1]), + Line(points[1], points[2]), + Line(points[1], points[3]), + Line(points[1], points[4]), + ) + wires.set_stroke(RED, 4) + self.dl_wire, self.dr_wire, self.ul_wire, self.ur_wire = wires + + self.trajectory = VMobject() + self.trajectory.set_points_as_corners(points[:3]) + self.ghost_trajectory = VMobject() + self.ghost_trajectory.set_points_as_corners([*points[:2], points[4]]) + VGroup(self.trajectory, self.ghost_trajectory).match_style( + self.wires + ) + + def add_diagram(self): + diagram = self.diagram = VGroup() + rect = diagram.rect = Rectangle( + height=4, width=5, + stroke_color=WHITE, + stroke_width=1, + fill_color=BLACK, + fill_opacity=0.9, + ) + rect.to_corner(UR) + diagram.add(rect) + + center = rect.get_center() + + mirror = diagram.mirror = VGroup( + Line(rect.get_left(), center + 1.5 * LEFT), + Line(center + 1.5 * LEFT, rect.get_right()), + ) + mirror.scale(0.8) + mirror[0].set_color((WHITE, GREY)) + mirror[1].set_color((GREY, WHITE)) + diagram.add(mirror) + + def set_as_reflection(m1, m2): + m1.become(m2) + m1.rotate(PI, axis=UP, about_point=center) + + def set_as_mirror_image(m1, m2): + m1.become(m2) + m1.rotate(PI, axis=RIGHT, about_point=center) + + wires = VGroup(*[ + Line(center + np.array([-1, -1.5, 0]), center) + for x in range(4) + ]) + dl_wire, dr_wire, ul_wire, ur_wire = wires + dr_wire.add_updater( + lambda m: set_as_reflection(m, dl_wire) + ) + ul_wire.add_updater( + lambda m: set_as_mirror_image(m, dl_wire) + ) + ur_wire.add_updater( + lambda m: set_as_mirror_image(m, dr_wire) + ) + + diagram.wires = wires + diagram.wires.set_stroke(RED, 2) + diagram.add(diagram.wires) + + def introduce_wires(self): + dl_wire = self.dl_wire + dr_wire = self.dr_wire + + def get_rect(wire): + rect = Rectangle( + width=wire.get_length(), + height=0.25, + color=YELLOW, + ) + rect.rotate(wire.get_angle()) + rect.move_to(wire) + return rect + + for wire in dl_wire, dr_wire: + self.play(ShowCreationThenFadeOut(get_rect(wire))) + self.play(*self.get_shooting_beam_anims(wire)) + self.wait() + + diagram = self.diagram.copy() + diagram.clear_updaters() + self.play( + FadeIn(diagram.rect), + ShowCreation(diagram.mirror), + LaggedStart(ShowCreation, diagram.wires), + run_time=1 + ) + self.remove(diagram) + self.add(self.diagram) + + self.wait() + + def show_illusion(self): + g_trajectory = self.ghost_trajectory + d_trajectory = self.d_trajectory = Line( + self.diagram.wires[0].get_start(), + self.diagram.wires[3].get_start(), + ) + d_trajectory.match_style(g_trajectory) + + g_trajectory.points[0] += 0.2 * RIGHT + 0.1 * DOWN + g_trajectory.make_jagged() + for x in range(3): + self.play( + *self.get_shooting_beam_anims(g_trajectory), + *self.get_shooting_beam_anims(d_trajectory), + ) + self.wait() + + def show_angles(self): + dl_wire = self.diagram.wires[0] + dr_wire = self.diagram.wires[1] + center = self.diagram.get_center() + arc_config = { + "radius": 0.5, + "arc_center": center, + } + + def get_dl_arc(): + return Arc( + start_angle=PI, + angle=dl_wire.get_angle(), + **arc_config, + ) + dl_arc = updating_mobject_from_func(get_dl_arc) + + def get_dr_arc(): + return Arc( + start_angle=0, + angle=dr_wire.get_angle() - PI, + **arc_config, + ) + dr_arc = updating_mobject_from_func(get_dr_arc) + + incidence = TextMobject("Incidence") + reflection = TextMobject("Reflection") + words = VGroup(incidence, reflection) + words.scale(0.75) + incidence.add_updater( + lambda m: m.next_to(dl_arc, LEFT, SMALL_BUFF) + ) + reflection.add_updater( + lambda m: m.next_to(dr_arc, RIGHT, SMALL_BUFF) + ) + for word in words: + word.set_background_stroke(width=0) + word.add_updater(lambda m: m.shift(SMALL_BUFF * DOWN)) + + self.add(incidence) + self.play( + ShowCreation(dl_arc), + UpdateFromAlphaFunc( + VMobject(), + lambda m, a: incidence.set_fill(opacity=a), + remover=True + ), + ) + self.wait() + self.add(reflection) + self.play( + ShowCreation(dr_arc), + UpdateFromAlphaFunc( + VMobject(), + lambda m, a: reflection.set_fill(opacity=a), + remover=True + ), + ) + self.wait() + + # Change dr wire angle + # dr_wire.suspend_updating() + dr_wire.clear_updaters() + for angle in [20 * DEGREES, -20 * DEGREES]: + self.play( + Rotate( + dr_wire, angle, + about_point=dr_wire.get_end(), + run_time=2, + ), + ) + self.play( + *self.get_shooting_beam_anims(self.ghost_trajectory), + *self.get_shooting_beam_anims(self.d_trajectory), + ) + self.wait() + + def show_reflecting_beam(self): + self.play( + *self.get_shooting_beam_anims(self.trajectory), + *self.get_shooting_beam_anims(self.ghost_trajectory), + ) + self.wait() diff --git a/active_projects/clacks/solution2/pi_creature_scenes.py b/old_projects/clacks/solution2/pi_creature_scenes.py similarity index 64% rename from active_projects/clacks/solution2/pi_creature_scenes.py rename to old_projects/clacks/solution2/pi_creature_scenes.py index 0189ea5f..fe8c3a0b 100644 --- a/active_projects/clacks/solution2/pi_creature_scenes.py +++ b/old_projects/clacks/solution2/pi_creature_scenes.py @@ -49,7 +49,16 @@ class OnAnsweringTwice(TeacherStudentsScene): class AskAboutEqualMassMomentumTransfer(TeacherStudentsScene): def construct(self): - pass + self.student_says("Why?") + self.change_student_modes("confused", "confused") + self.wait() + self.play( + RemovePiCreatureBubble(self.students[2]), + self.teacher.change, "raise_right_hand" + ) + self.change_all_student_modes("pondering") + self.look_at(self.hold_up_spot + 2 * UP) + self.wait(5) class ComplainAboutRelevanceOfAnalogy(TeacherStudentsScene): @@ -76,3 +85,34 @@ class ComplainAboutRelevanceOfAnalogy(TeacherStudentsScene): self.hold_up_spot + UP, ) self.wait(3) + + +class ReplaceOneTrickySceneWithAnother(TeacherStudentsScene): + def construct(self): + self.student_says( + "This replaces one tricky\\\\problem with another", + student_index=1, + target_mode="sassy", + added_anims=[self.teacher.change, "happy"], + ) + self.change_student_modes("erm", "sassy", "angry") + self.wait(4) + self.play( + RemovePiCreatureBubble(self.students[1]), + self.teacher.change, "raise_right_hand", + self.get_student_changes(*3 * ["pondering"]) + ) + self.look_at(self.hold_up_spot + 2 * UP) + self.wait(5) + + +class NowForTheGoodPart(TeacherStudentsScene): + def construct(self): + self.teacher_says( + r"Now for the \\ good part!", + target_mode="hooray", + added_anims=[self.get_student_changes( + "hooray", "surprised", "happy" + )], + ) + self.wait(2) diff --git a/active_projects/clacks/solution2/position_phase_space.py b/old_projects/clacks/solution2/position_phase_space.py similarity index 52% rename from active_projects/clacks/solution2/position_phase_space.py rename to old_projects/clacks/solution2/position_phase_space.py index 2df54269..6cb2d712 100644 --- a/active_projects/clacks/solution2/position_phase_space.py +++ b/old_projects/clacks/solution2/position_phase_space.py @@ -1,7 +1,7 @@ from big_ol_pile_of_manim_imports import * -from active_projects.clacks.question import Block -from active_projects.clacks.question import Wall -from active_projects.clacks.question import ClackFlashes +from old_projects.clacks.question import Block +from old_projects.clacks.question import Wall +from old_projects.clacks.question import ClackFlashes class PositionPhaseSpaceScene(Scene): @@ -18,7 +18,7 @@ class PositionPhaseSpaceScene(Scene): "block1_config": { "mass": 10, "distance": 9, - "velocity": 1, + "velocity": -1, "width": 1.6, }, "block2_config": { @@ -47,17 +47,36 @@ class PositionPhaseSpaceScene(Scene): "radius": 0.05, }, "ps_d2_label_vect": RIGHT, + "ps_x_line_config": { + "color": GREEN, + "stroke_width": 2, + }, + "ps_y_line_config": { + "color": RED, + "stroke_width": 2, + }, "clack_sound": "clack", "mirror_line_class": Line, "mirror_line_style": { "stroke_color": WHITE, "stroke_width": 1, }, - "d1_eq_e2_line_color": GREEN_SCREEN, + "d1_eq_d2_line_color": MAROON_B, + "d1_eq_d2_tex": "d1 = d2", "trajectory_style": { "stroke_color": YELLOW, "stroke_width": 2, - } + }, + "ps_velocity_vector_length": 0.75, + "ps_velocity_vector_config": { + "color": PINK, + "rectangular_stem_width": 0.025, + "tip_length": 0.15, + }, + "block_velocity_vector_length_multiple": 2, + "block_velocity_vector_config": { + "color": PINK, + }, } def setup(self): @@ -186,6 +205,7 @@ class PositionPhaseSpaceScene(Scene): v1 = self.block1.velocity w2 = self.block2.get_width() h2 = self.block2.get_height() + start_d1, start_d2 = self.get_ds() ps_speed = np.sqrt(m1) * abs(v1) theta = np.arctan(np.sqrt(m2 / m1)) @@ -202,7 +222,7 @@ class PositionPhaseSpaceScene(Scene): point[1] / np.sqrt(m2), ) - ps_point = ds_to_ps_point(*self.get_ds()) + ps_point = ds_to_ps_point(start_d1, start_d2) wedge_corner = ds_to_ps_point(w2, w2) ps_point -= wedge_corner y = ps_point[1] @@ -225,38 +245,38 @@ class PositionPhaseSpaceScene(Scene): clack_data.append((time, loc1, loc2)) return clack_data - def get_clack_flashes(self): - pass # TODO - def tie_ps_point_to_time_tracker(self): - time_tracker = self.get_time_tracker( - time=self.total_sliding_time - ) + if not hasattr(self, "sliding_time_tracker"): + self.sliding_time_tracker = self.get_time_tracker() def update_ps_point(p): - time = time_tracker.get_value() + time = self.sliding_time_tracker.get_value() ds = self.time_to_ds(time) p.move_to(self.ds_to_point(*ds)) + self.ps_point.add_updater(update_ps_point) - self.add(time_tracker, self.ps_point) + self.add(self.sliding_time_tracker, self.ps_point) def add_clack_flashes(self): - clack_data = self.get_clack_data() - self.clack_times = [ - time for (time, loc1, loc2) in clack_data - ] - self.block_flashes = ClackFlashes([ - (loc1, time) - for (time, loc1, loc2) in clack_data - ]) - self.ps_flashes = ClackFlashes([ - (loc2, time) - for (time, loc1, loc2) in clack_data - ]) - self.add( - self.block_flashes, - self.ps_flashes, - ) + if hasattr(self, "flash_anims"): + self.add(*self.flash_anims) + else: + clack_data = self.get_clack_data() + self.clack_times = [ + time for (time, loc1, loc2) in clack_data + ] + self.block_flashes = ClackFlashes([ + (loc1, time) + for (time, loc1, loc2) in clack_data + ]) + self.ps_flashes = ClackFlashes([ + (loc2, time) + for (time, loc1, loc2) in clack_data + ]) + self.flash_anims = [self.block_flashes, self.ps_flashes] + for anim in self.flash_anims: + anim.get_time = self.sliding_time_tracker.get_value + self.add(*self.flash_anims) def get_continually_building_trajectory(self): trajectory = VMobject() @@ -296,22 +316,23 @@ class PositionPhaseSpaceScene(Scene): self.add(self.get_continually_building_trajectory()) def end_sliding(self): + self.continual_update(dt=0) self.ps_point.clear_updaters() - self.remove(self.time_tracker) - to_remove = ["block_flashes", "ps_flashes"] - for attr in to_remove: - if hasattr(self, attr): - self.remove(getattr(self, attr)) + if hasattr(self, "sliding_time_tracker"): + self.remove(self.sliding_time_tracker) + if hasattr(self, "flash_anims"): + self.remove(*self.flash_anims) if hasattr(self, "trajectory"): self.trajectory.suspend_updating() - total_time = self.time_tracker.get_value() - self.total_sliding_time += total_time + old_total_sliding_time = self.total_sliding_time + new_total_sliding_time = self.sliding_time_tracker.get_value() + self.total_sliding_time = new_total_sliding_time for time in self.clack_times: - if time < total_time: - offset = total_time - time + if old_total_sliding_time < time < new_total_sliding_time: + offset = time - new_total_sliding_time self.add_sound( "clack", - time_offset=-offset, + time_offset=offset, ) def slide(self, time, stop_condition=None): @@ -409,21 +430,21 @@ class PositionPhaseSpaceScene(Scene): def get_axes_labels(self, axes, with_sqrts=None): if with_sqrts is None: with_sqrts = self.rescale_coordinates - x_label = TexMobject("x = ", "d_1") - y_label = TexMobject("y = ", "d_2") + x_label = TexMobject("x", "=", "d_1") + y_label = TexMobject("y", "=", "d_2") labels = VGroup(x_label, y_label) if with_sqrts: additions = map(TexMobject, [ "\\sqrt{m_1}", "\\sqrt{m_2}" ]) for label, addition in zip(labels, additions): - addition.move_to(label[1], DL) - label[1].next_to( + addition.move_to(label[2], DL) + label[2].next_to( addition, RIGHT, SMALL_BUFF, aligned_edge=DOWN ) addition[2:].set_color(BLUE) - label.add(addition) + label.submobjects.insert(2, addition) x_label.next_to(axes.x_axis.get_right(), DL, MED_SMALL_BUFF) y_label.next_to(axes.y_axis.get_top(), DR, MED_SMALL_BUFF) for label in labels: @@ -447,8 +468,7 @@ class PositionPhaseSpaceScene(Scene): y_axis_point[1] = point[1] return DashedLine( y_axis_point, point, - color=GREEN, - stroke_width=2, + **self.ps_x_line_config, ) self.x_line = updating_mobject_from_func(get_x_line) return self.x_line @@ -461,8 +481,7 @@ class PositionPhaseSpaceScene(Scene): x_axis_point[0] = point[0] return DashedLine( x_axis_point, point, - color=RED, - stroke_width=2, + **self.ps_y_line_config, ) self.y_line = updating_mobject_from_func(get_y_line) return self.y_line @@ -570,11 +589,11 @@ class PositionPhaseSpaceScene(Scene): end = self.ds_to_point(15, 15) line = self.d1_eq_d2_line = self.mirror_line_class(start, end) line.set_style(**self.mirror_line_style) - line.set_color(self.d1_eq_e2_line_color) + line.set_color(self.d1_eq_d2_line_color) return self.d1_eq_d2_line def get_d1_eq_d2_label(self): - label = TexMobject("d1 = d2") + label = TexMobject(self.d1_eq_d2_tex) label.scale(0.75) line = self.d1_eq_d2_line point = interpolate( @@ -611,14 +630,60 @@ class PositionPhaseSpaceScene(Scene): ) return time_tracker + # Things associated with velocity + def get_ps_velocity_vector(self, trajectory): + vector = Vector( + self.ps_velocity_vector_length * LEFT, + **self.ps_velocity_vector_config, + ) + + def update_vector(v): + anchors = trajectory.get_anchors() + index = len(anchors) - 2 + vect = np.array(ORIGIN) + while get_norm(vect) == 0 and index > 0: + p0, p1 = anchors[index:index + 2] + vect = p1 - p0 + index -= 1 + angle = angle_of_vector(vect) + point = self.ps_point.get_location() + v.set_angle(angle) + v.shift(point - v.get_start()) + vector.add_updater(update_vector) + self.ps_velocity_vector = vector + return vector + + def get_block_velocity_vectors(self, ps_vect): + blocks = self.blocks + vectors = VGroup(*[ + Vector(LEFT, **self.block_velocity_vector_config) + for x in range(2) + ]) + # TODO: Put in config + vectors[0].set_color(GREEN) + vectors[1].set_color(RED) + + def update_vectors(vs): + v_2d = ps_vect.get_vector()[:2] + v_2d *= self.block_velocity_vector_length_multiple + for v, coord, block in zip(vs, v_2d, blocks): + v.put_start_and_end_on(ORIGIN, coord * RIGHT) + start = block.get_edge_center(v.get_vector()) + v.shift(start) + vectors.add_updater(update_vectors) + + self.block_velocity_vectors = vectors + return vectors + class IntroducePositionPhaseSpace(PositionPhaseSpaceScene): CONFIG = { "rescale_coordinates": False, + "d1_eq_d2_tex": "x = y", "block1_config": { "velocity": 1.5, }, - "slide_wait_time": 30, + "slide_wait_time": 12, } def setup(self): @@ -711,7 +776,6 @@ class IntroducePositionPhaseSpace(PositionPhaseSpaceScene): sliding_trajectory = self.get_continually_building_trajectory() self.add(sliding_trajectory, self.ps_dot) self.wait(self.slide_wait_time) - self.end_sliding() def show_w2_line(self): line = self.d2_eq_w2_line @@ -719,7 +783,9 @@ class IntroducePositionPhaseSpace(PositionPhaseSpaceScene): self.play(ShowCreation(line)) self.play(FadeInFromDown(label)) - self.wait() + self.wait(self.slide_wait_time) + self.end_sliding() + self.wait(self.slide_wait_time) class SpecialShowPassingFlash(ShowPassingFlash): @@ -745,6 +811,7 @@ class EqualMassCase(PositionPhaseSpaceScene): "velocity": 1.5, }, "rescale_coordinates": False, + "d1_eq_d2_tex": "x = y", } def setup(self): @@ -922,14 +989,14 @@ class EqualMassCase(PositionPhaseSpaceScene): d1, d2 = self.get_ds() d1 = int(d1) d2 = int(d2) - w2 = self.block2.get_width() + # w2 = self.block2.get_width() ps_point = self.ps_point points = [] while d1 > d2: points.append(self.ds_to_point(d1, d2)) d1 -= 1 - while d2 >= int(w2): + while d2 >= 1: points.append(self.ds_to_point(d1, d2)) d2 -= 1 points += list(reversed(points))[1:] @@ -1104,7 +1171,7 @@ class FailedAngleRelation(PositionPhaseSpaceScene): result = VGroup( TextMobject("Angle of incidence"), TexMobject("\\ne").rotate(90 * DEGREES), - TextMobject("Angle of refraction") + TextMobject("Angle of reflection") ) result.arrange_submobjects(DOWN) result.set_stroke(BLACK, 5, background=True) @@ -1186,7 +1253,10 @@ class RescaleCoordinates(PositionPhaseSpaceScene, MovingCameraScene): self.play( Transform( axes.labels[index], - new_axes_labels[index][:2], + VGroup( + *new_axes_labels[index][:2], + new_axes_labels[index][3] + ), ), GrowFromCenter(new_axes_labels[index][2]) ) @@ -1282,7 +1352,7 @@ class RescaleCoordinates(PositionPhaseSpaceScene, MovingCameraScene): rect = ScreenRectangle(height=FRAME_HEIGHT + 10) inner_rect = ScreenRectangle(height=FRAME_HEIGHT) rect.add_subpath(inner_rect.points[::-1]) - rect.set_fill(DARK_GREY, opacity=1) + rect.set_fill("#333333", opacity=1) frame = self.camera_frame self.begin_sliding() @@ -1328,10 +1398,978 @@ class RescaleCoordinatesMass64(RescaleCoordinatesMass16): "block2_config": {"distance": 3}, } - def construct(self): - self.put_into_frame() + +class RescaleCoordinatesMass100(RescaleCoordinatesMass16): + CONFIG = { + "block1_config": { + "mass": 100, + "distance": 6, + "velocity": 0.5, + }, + "block2_config": {"distance": 2}, + "wait_time": 25, + } -class NewSceneName(Scene): +class IntroduceVelocityVector(PositionPhaseSpaceScene, MovingCameraScene): + CONFIG = { + "zoom": True, + "ps_x_line_config": { + "color": WHITE, + "stroke_width": 1, + "stroke_opacity": 0.5, + }, + "ps_y_line_config": { + "color": WHITE, + "stroke_width": 1, + "stroke_opacity": 0.5, + }, + "axes_center": 6 * LEFT + 0.65 * DOWN, + "slide_time": 20, + "new_vect_config": { + "tip_length": 0.1, + "rectangular_stem_width": 0.02, + } + } + + def setup(self): + MovingCameraScene.setup(self) + PositionPhaseSpaceScene.setup(self) + self.add( + self.floor, + self.wall, + self.blocks, + self.axes, + self.d1_eq_d2_line, + self.d1_eq_d2_label, + self.d2_eq_w2_line, + self.x_line, + self.y_line, + self.ps_dot, + ) + def construct(self): - pass + self.show_velocity_vector() + self.contrast_with_physical_velocities() + self.zoom_in_on_vector() + self.break_down_components() + self.zoom_out() + self.relate_x_dot_y_dot_to_v1_v2() + self.calculate_magnitude() + self.let_process_play_out() + + def show_velocity_vector(self): + self.slide(2) + ps_vect = self.get_ps_velocity_vector(self.trajectory) + self.play(GrowArrow(ps_vect)) + self.play(ShowCreationThenFadeAround(ps_vect)) + self.wait() + + def contrast_with_physical_velocities(self): + ps_vect = self.ps_velocity_vector + block_vectors = self.get_block_velocity_vectors(ps_vect) + + self.play(LaggedStart(GrowArrow, block_vectors)) + self.play(Rotating( + ps_vect, + angle=TAU, + about_point=ps_vect.get_start(), + run_time=5, + rate_func=smooth, + )) + self.wait() + self.slide_until(lambda: self.get_d2() < 2.5) + + def zoom_in_on_vector(self): + if not self.zoom: + self.wait(3) + return + ps_vect = self.ps_velocity_vector + new_vect = Arrow( + ps_vect.get_start(), + ps_vect.get_end(), + buff=0, + **self.new_vect_config + ) + new_vect.match_style(ps_vect) + + camera_frame = self.camera_frame + camera_frame.save_state() + point = self.ps_point.get_location() + point += MED_SMALL_BUFF * DOWN + self.play( + camera_frame.scale, 0.25, {"about_point": point}, + Transform(ps_vect, new_vect), + run_time=2, + ) + self.wait() + + def break_down_components(self): + # Create vectors + ps_vect = self.ps_velocity_vector + start = ps_vect.get_start() + end = ps_vect.get_end() + ul_corner = np.array(start) + dr_corner = np.array(start) + ul_corner[0] = end[0] + dr_corner[1] = end[1] + + x_vect = Arrow( + start, ul_corner, + buff=0, + **self.new_vect_config + ) + y_vect = Arrow( + start, dr_corner, + buff=0, + **self.new_vect_config + ) + x_vect.set_fill(GREEN, opacity=0.75) + y_vect.set_fill(RED, opacity=0.75) + vects = VGroup(x_vect, y_vect) + + # Projection lines + x_line, y_line = [ + DashedLine( + ps_vect.get_end(), + vect.get_end(), + dashed_segment_length=0.01, + color=vect.get_color(), + ) + for vect in (x_vect, y_vect) + ] + self.projection_lines = VGroup(x_line, y_line) + + # Vector labels + dx_label = TexMobject("\\frac{dx}{dt}") + dy_label = TexMobject("\\frac{dy}{dt}") + labels = VGroup(dx_label, dy_label) + for label, arrow, direction in zip(labels, vects, [UP, RIGHT]): + label.scale(0.25) + buff = 0.25 * SMALL_BUFF + label.next_to(arrow, direction, buff=buff) + label.set_stroke(BLACK, 3, background=True) + + if not self.zoom: + self.grow_labels(labels) + + self.play( + TransformFromCopy(ps_vect, x_vect), + ShowCreation(x_line), + ) + self.play(FadeInFrom(dx_label, 0.25 * DOWN)) + self.wait() + self.play( + TransformFromCopy(ps_vect, y_vect), + ShowCreation(y_line), + ) + self.play(FadeInFrom(dy_label, 0.25 * LEFT)) + self.wait() + + # Ask about dx_dt + randy = Randolph() + randy.match_height(dx_label) + randy.next_to(dx_label, LEFT, SMALL_BUFF) + randy.change("confused", dx_label) + randy.save_state() + randy.fade(1) + randy.change("plain") + + self.play(Restore(randy)) + self.play(WiggleOutThenIn(dx_label)) + self.play(Blink(randy)) + self.play(FadeOut(randy)) + + self.derivative_labels = labels + self.component_vectors = vects + + def zoom_out(self): + if not self.zoom: + self.wait(2) + return + labels = self.derivative_labels + self.play( + Restore(self.camera_frame), + ApplyFunction(self.grow_labels, labels), + run_time=2 + ) + + def relate_x_dot_y_dot_to_v1_v2(self): + derivative_labels = self.derivative_labels.copy() + dx_label, dy_label = derivative_labels + x_label, y_label = self.axes.labels + m_part = x_label[2] + block1 = self.block1 + block_vectors = self.block_velocity_vectors + + x_eq = x_label[1] + dx_eq = TexMobject("=") + dx_eq.next_to( + x_eq, DOWN, + buff=LARGE_BUFF, + aligned_edge=RIGHT, + ) + for label in derivative_labels: + label.generate_target() + label.target.scale(1.5) + dx_label.target.next_to(dx_eq, LEFT) + dx_rhs = TexMobject("\\sqrt{m_1}", "v_1") + dx_rhs[0][2:].set_color(BLUE) + dx_rhs[1].set_color(GREEN) + dx_rhs.next_to(dx_eq, RIGHT) + alt_v1 = dx_rhs[1].copy() + + self.play(ShowCreationThenFadeAround(x_label)) + self.play(MoveToTarget(dx_label)) + self.play(TransformFromCopy(x_eq, dx_eq)) + self.wait() + self.play( + VGroup(block1, m_part).shift, SMALL_BUFF * UP, + rate_func=wiggle, + ) + self.wait() + self.d1_brace.update() + self.d1_label.update() + self.play( + ShowCreationThenFadeAround(x_label[3]), + FadeIn(self.d1_brace), + FadeIn(self.d1_label), + ) + self.wait() + self.play( + TransformFromCopy(x_label[3], dx_rhs[1]), + TransformFromCopy(x_label[2], VGroup(dx_rhs[0])), + ) + block_vectors.suspend_updating() + self.play(alt_v1.next_to, block_vectors[0], UP, SMALL_BUFF) + self.play( + Rotate( + block_vectors[0], 10 * DEGREES, + about_point=block_vectors[0].get_start(), + rate_func=wiggle, + run_time=1, + ) + ) + self.play(FadeOut(alt_v1)) + block_vectors.resume_updating() + self.wait() + + # dy_label + y_eq = y_label[1] + dy_eq = TexMobject("=") + dy_eq.next_to(y_eq, DOWN, LARGE_BUFF) + dy_label.target.next_to(dy_eq, LEFT) + dy_rhs = TexMobject("\\sqrt{m_2}", "v_2") + dy_rhs[0][2:].set_color(BLUE) + dy_rhs[1].set_color(RED) + dy_rhs.next_to(dy_eq, RIGHT) + VGroup(dy_label.target, dy_eq, dy_rhs).align_to(y_label, LEFT) + alt_v2 = dy_rhs[1].copy() + self.play(MoveToTarget(dy_label)) + self.play( + Write(dy_eq), + Write(dy_rhs), + ) + self.play(alt_v2.next_to, block_vectors[1], UP, SMALL_BUFF) + self.wait() + self.play(FadeOut(alt_v2)) + self.wait() + + self.derivative_equations = VGroup( + VGroup(dx_label, dx_eq, dx_rhs), + VGroup(dy_label, dy_eq, dy_rhs), + ) + + def calculate_magnitude(self): + corner_rect = Rectangle( + stroke_color=WHITE, + stroke_width=1, + fill_color=BLACK, + fill_opacity=1, + height=2.5, + width=8.5, + ) + corner_rect.to_corner(UR, buff=0) + + ps_vect = self.ps_velocity_vector + big_ps_vect = Arrow( + ps_vect.get_start(), ps_vect.get_end(), + buff=0, + ) + big_ps_vect.match_style(ps_vect) + big_ps_vect.scale(1.5) + magnitude_bars = TexMobject("||", "||") + magnitude_bars.match_height( + big_ps_vect, stretch=True + ) + rhs_scale_val = 0.8 + rhs = TexMobject( + "=\\sqrt{" + "\\left( dx/dt \\right)^2 + " + "\\left( dy/dt \\right)^2" + "}" + ) + rhs.scale(rhs_scale_val) + group = VGroup( + magnitude_bars[0], big_ps_vect, + magnitude_bars[1], rhs + ) + group.arrange_submobjects(RIGHT) + group.next_to(corner_rect.get_corner(UL), DR) + + new_rhs = TexMobject( + "=", "\\sqrt", "{m_1(v_1)^2 + m_2(v_2)^2}", + tex_to_color_map={ + "m_1": BLUE, + "m_2": BLUE, + "v_1": GREEN, + "v_2": RED, + } + ) + new_rhs.scale(rhs_scale_val) + new_rhs.next_to(rhs, DOWN, aligned_edge=LEFT) + + final_rhs = TexMobject( + "=", "\\sqrt{2(\\text{Kinetic energy})}" + ) + final_rhs.scale(rhs_scale_val) + final_rhs.next_to(new_rhs, DOWN, aligned_edge=LEFT) + + self.play( + FadeIn(corner_rect), + TransformFromCopy(ps_vect, big_ps_vect) + ) + self.play(Write(magnitude_bars), Write(rhs[0])) + self.wait() + self.play(Write(rhs[1:])) + self.wait() + self.play(FadeInFrom(new_rhs, UP)) + for equation in self.derivative_equations: + self.play(ShowCreationThenFadeAround(equation)) + self.wait() + self.play(FadeInFrom(final_rhs, UP)) + self.wait() + + def let_process_play_out(self): + self.play(*map(FadeOut, [ + self.projection_lines, + self.derivative_labels, + self.component_vectors, + self.d1_brace, + self.d1_label, + ])) + self.add(self.blocks, self.derivative_equations) + self.blocks.resume_updating() + self.slide(self.slide_time) + + # + def grow_labels(self, labels): + for label, vect in zip(labels, [DOWN, LEFT]): + p = label.get_edge_center(vect) + p += SMALL_BUFF * vect + label.scale(2.5, about_point=p) + return labels + + +class IntroduceVelocityVectorWithoutZoom(IntroduceVelocityVector): + CONFIG = { + "zoom": False, + } + + +class ShowMomentumConservation(IntroduceVelocityVector): + CONFIG = { + "ps_velocity_vector_length": 1.25, + "block_velocity_vector_length_multiple": 1, + "block1_config": { + "distance": 7, + }, + "axes_config": { + "y_max": 11, + }, + "axes_center": 6.5 * LEFT + 1.2 * DOWN, + "floor_y": -3.75, + "wait_time": 15, + } + + def construct(self): + self.add_velocity_vectors() + self.contrast_d1_d2_line_with_xy_line() + self.rearrange_for_slope() + self.up_to_first_collision() + self.ask_what_next() + self.show_conservation_of_momentum() + self.show_rate_of_change_vector() + self.show_sqrty_m_vector() + self.show_dot_product() + self.show_same_angles() + self.show_horizontal_bounce() + self.let_process_play_out() + + def add_velocity_vectors(self): + self.slide(1) + self.ps_vect = self.get_ps_velocity_vector(self.trajectory) + self.block_vectors = self.get_block_velocity_vectors(self.ps_vect) + self.play( + GrowArrow(self.ps_vect), + LaggedStart(GrowArrow, self.block_vectors, run_time=1), + ) + self.add(self.ps_vect, self.block_vectors) + + def contrast_d1_d2_line_with_xy_line(self): + line = self.d1_eq_d2_line + label = self.d1_eq_d2_label + label.to_edge(RIGHT, buff=1.1) + label.shift(0.65 * DOWN) + + xy_line = line.copy() + xy_line.set_stroke(YELLOW, 3) + xy_line.set_angle(45 * DEGREES) + xy_label = TexMobject("x = y") + xy_label.next_to(ORIGIN, DOWN, SMALL_BUFF) + xy_label.rotate(45 * DEGREES, about_point=ORIGIN) + xy_label.shift(xy_line.point_from_proportion(0.2)) + self.xy_group = VGroup(xy_line, xy_label) + + self.play( + ShowPassingFlash( + line.copy().set_stroke(YELLOW, 4) + ), + Write(label), + ) + self.play( + TransformFromCopy(line, xy_line, run_time=2) + ) + self.play(Write(xy_label)) + self.wait() + + def rearrange_for_slope(self): + eqs = VGroup(*reversed(self.axes.labels)).copy() + y_eq, x_eq = eqs + for eq in eqs: + point = VectorizedPoint(eq[1].get_center()) + eq.submobjects.insert(1, point) + eq.submobjects[3] = eq[3].submobjects[0] + eq.generate_target() + eqs_targets = VGroup(*[eq.target for eq in eqs]) + + new_eqs = VGroup( + TexMobject("{y", "\\over", "\\sqrt{m_2}}", "=", "d_2"), + TexMobject("{x", "\\over", "\\sqrt{m_1}}", "=", "d_1"), + ) + new_x_eq, new_y_eq = new_eqs + # Shuffle to align with x_eq and y_eq + for new_eq in new_eqs: + new_eq[2][2:].set_color(BLUE) + new_eq.submobjects = [new_eq[i] for i in [0, 1, 3, 2, 4]] + + eqs_targets.arrange_submobjects(DOWN, buff=LARGE_BUFF) + eqs_targets.move_to(RIGHT).to_edge(UP) + for eq, new_eq in zip(eqs_targets, new_eqs): + new_eq.move_to(eq) + + self.play(LaggedStart(MoveToTarget, eqs, lag_ratio=0.7)) + self.play(*[ + Transform( + eq, new_eq, + path_arc=-90 * DEGREES, + ) + for eq, new_eq in zip(eqs, new_eqs) + ]) + self.wait() + + # Shuffle back + for eq in eqs: + eq[2][2:].set_color(BLUE) + eq.submobjects = [eq[i] for i in [0, 1, 3, 2, 4]] + + # Set equal + equals = TexMobject("=") + for eq in eqs: + eq.generate_target() + VGroup( + x_eq.target[4], + x_eq.target[3], + x_eq.target[:3], + ).arrange_submobjects(RIGHT) + for p1, p2 in zip(x_eq, x_eq.target): + p2.align_to(p1, DOWN) + group = VGroup(y_eq.target, equals, x_eq.target) + group.arrange_submobjects(RIGHT) + x_eq.target.align_to(y_eq.target, DOWN) + equals.align_to(y_eq.target[3], DOWN) + group.to_edge(UP, buff=MED_SMALL_BUFF) + group.to_edge(RIGHT, buff=3) + + self.play( + MoveToTarget(y_eq), + MoveToTarget(x_eq, path_arc=90 * DEGREES), + GrowFromCenter(equals) + ) + self.wait() + + # Simplify + final_eq = TexMobject( + "y", "=", + "{\\sqrt{m_2}", "\\over", "\\sqrt{m_1}}", + "x", + ) + for part in final_eq.get_parts_by_tex("sqrt"): + part[2:].set_color(BLUE) + m_part = final_eq[2:5] + + final_eq.next_to(group, DOWN) + final_eq.shift(0.4 * UP) + movers = VGroup( + y_eq[0], equals.submobjects[0], + y_eq[2], y_eq[1], x_eq[2], + x_eq[0] + ).copy() + for mover, part in zip(movers, final_eq): + mover.target = part + self.play( + LaggedStart( + MoveToTarget, movers, + path_arc=30 * DEGREES, + lag_ratio=0.9 + ), + VGroup(x_eq, equals, y_eq).scale, + 0.7, {"about_edge": UP}, + ) + self.remove(movers) + self.add(final_eq) + self.wait() + + # Highlight slope + flash_line = self.d1_eq_d2_line.copy() + flash_line.set_stroke(YELLOW, 5) + self.play(ShowPassingFlash(flash_line)) + self.play(ShowCreationThenFadeAround(m_part)) + self.wait() + + # Tuck away slope in mind + slope = m_part.copy() + slope.generate_target() + randy = Randolph(height=1.5) + randy.next_to(final_eq, LEFT, MED_SMALL_BUFF) + randy.align_to(self.d2_eq_w2_line, DOWN) + bubble = ThoughtBubble( + height=1.3, width=1.3, direction=RIGHT, + ) + bubble.pin_to(randy) + slope.target.scale(0.5) + slope.target.move_to(bubble.get_bubble_center()) + randy.change("pondering", slope.target) + randy.save_state() + randy.change("plane") + randy.fade(1) + + self.play( + Restore(randy), + Write(bubble), + MoveToTarget(slope) + ) + self.play(Blink(randy)) + + self.thinking_on_slope_group = VGroup( + randy, bubble, slope, + ) + + self.to_fade = VGroup( + eqs, equals, final_eq, + self.thinking_on_slope_group, + self.xy_group, + ) + + def up_to_first_collision(self): + self.begin_sliding() + self.play(FadeOut(self.to_fade)) + self.wait_until( + lambda: abs(self.ps_velocity_vector.get_vector()[1]) > 0.01 + ) + self.end_sliding() + self.wait(3 + 3 / 60) # Final cut reasons + + def ask_what_next(self): + ps_vect = self.ps_velocity_vector + question = TextMobject("What next?") + question.set_background_stroke(color=BLACK, width=3) + question.next_to(self.ps_point, UP) + + self.play(FadeInFrom(question, DOWN)) + ps_vect.suspend_updating() + angles = [0.75 * PI, -0.5 * PI, -0.25 * PI] + for last_angle, angle in zip(np.cumsum([0] + angles), angles): + # This is dumb and shouldn't be needed + ps_vect.rotate(last_angle, about_point=ps_vect.get_start()) + target = ps_vect.copy() + target.rotate( + angle, + about_point=ps_vect.get_start() + ) + self.play( + Transform( + ps_vect, target, + path_arc=angle + ), + ) + ps_vect.resume_updating() + + self.whats_next_question = question + + def show_conservation_of_momentum(self): + equation = self.get_momentum_equation() + + # Main equation + self.play(FadeInFromDown(equation)) + for part in equation[:2], equation[3:5]: + outline = part.copy() + outline.set_fill(opacity=0) + outline.set_stroke(YELLOW, 3) + self.play(ShowPassingFlash( + outline, + run_time=1.5 + )) + self.wait(0.5) + + # Dot product + dot_product = self.get_dot_product() + dot_product.next_to(equation, DOWN) + sqrty_m_array = dot_product[0] + + x_label, y_label = self.axes.labels + + self.play( + FadeOut(self.whats_next_question), + FadeIn(dot_product), + Transform( + x_label[2].copy(), + sqrty_m_array.get_entries()[0], + remover=True, + ), + Transform( + y_label[2].copy(), + sqrty_m_array.get_entries()[1], + remover=True, + ), + ) + + self.momentum_equation = equation + self.dot_product = dot_product + + def show_rate_of_change_vector(self): + ps_vect = self.ps_velocity_vector + original_d_array = self.dot_product[2] + + d_array = original_d_array.copy() + d_array.generate_target() + d_array.scale(0.75) + d_array.add_updater(lambda m: m.next_to( + ps_vect.get_end(), + np.sign(ps_vect.get_vector()[0]) * RIGHT, + SMALL_BUFF + )) + + self.play(TransformFromCopy(original_d_array, d_array)) + self.wait() + + self.d_array = d_array + + def show_sqrty_m_vector(self): + original_sqrty_m_array = self.dot_product[0] + sqrty_m_vector = Arrow( + self.ds_to_point(0, 0), + # self.ds_to_point(1, 1), + self.ds_to_point(2, 2), + buff=0, + color=YELLOW, + ) + + sqrty_m_array = original_sqrty_m_array.deepcopy() + sqrty_m_array.scale(0.75) + sqrty_m_array.next_to( + sqrty_m_vector.get_end(), UP, SMALL_BUFF + ) + + rise = DashedLine( + sqrty_m_vector.get_end(), + sqrty_m_vector.get_corner(DR), + color=RED, + ) + run = DashedLine( + sqrty_m_vector.get_corner(DR), + sqrty_m_vector.get_start(), + color=GREEN, + ) + sqrty_m_array.add_background_to_entries() + run_label, rise_label = sqrty_m_array.get_entries().copy() + rise_label.next_to(rise, RIGHT, SMALL_BUFF) + run_label.next_to(run, DOWN, SMALL_BUFF) + + randy_group = self.thinking_on_slope_group + randy_group.align_to(self.d2_eq_w2_line, DOWN) + randy_group.to_edge(LEFT) + randy_group.shift(2 * RIGHT) + + self.play(GrowArrow(sqrty_m_vector)) + self.play(TransformFromCopy( + original_sqrty_m_array, sqrty_m_array, + )) + self.play(FadeIn(randy_group)) + self.play( + ShowCreation(rise), + TransformFromCopy( + sqrty_m_array.get_entries()[1], + rise_label, + ), + ) + self.add(run, randy_group) + self.play( + ShowCreation(run), + TransformFromCopy( + sqrty_m_array.get_entries()[0], + run_label, + ), + ) + self.wait() + self.play(FadeOut(randy_group)) + self.play(FadeOut(VGroup( + rise, run, rise_label, run_label, + ))) + + # move to ps_point + point = self.ps_point.get_location() + sqrty_m_vector.generate_target() + sqrty_m_vector.target.shift( + point - sqrty_m_vector.get_start() + ) + sqrty_m_array.generate_target() + sqrty_m_array.target.next_to( + sqrty_m_vector.target.get_end(), + RIGHT, SMALL_BUFF, + ) + sqrty_m_array.target.shift(SMALL_BUFF * UP) + self.play( + MoveToTarget(sqrty_m_vector), + MoveToTarget(sqrty_m_array), + run_time=2 + ) + + self.sqrty_m_vector = sqrty_m_vector + self.sqrty_m_array = sqrty_m_array + + def show_dot_product(self): + # Highlight arrays + d_array = self.d_array + big_d_array = self.dot_product[2] + m_array = self.sqrty_m_array + big_m_array = self.dot_product[0] + + self.play( + ShowCreationThenFadeAround(big_d_array), + ShowCreationThenFadeAround(d_array), + ) + self.play( + ShowCreationThenFadeAround(big_m_array), + ShowCreationThenFadeAround(m_array), + ) + + # Before and after + ps_vect = self.ps_velocity_vector + theta = np.arctan(np.sqrt(self.block2.mass / self.block1.mass)) + self.theta = theta + + ps_vect.suspend_updating() + kwargs = {"about_point": ps_vect.get_start()} + for x in range(2): + for u in [-1, 1]: + ps_vect.rotate(u * 2 * theta, **kwargs) + self.continual_update(dt=0) + self.wait() + ps_vect.resume_updating() + + # Circle + circle = Circle( + radius=ps_vect.get_length(), + arc_center=ps_vect.get_start(), + color=RED, + stroke_width=1, + ) + self.play( + Rotating( + ps_vect, + about_point=ps_vect.get_start(), + run_time=5, + rate_func=lambda t: smooth(t, 3), + ), + FadeIn(circle), + ) + self.wait() + + self.ps_vect_circle = circle + + def show_same_angles(self): + # circle = self.ps_vect_circle + ps_vect = self.ps_velocity_vector + ps_point = self.ps_point + point = ps_point.get_center() + ghost_ps_vect = ps_vect.copy() + ghost_ps_vect.clear_updaters() + ghost_ps_vect.set_fill(opacity=0.5) + theta = self.theta + ghost_ps_vect.rotate( + -2 * theta, + about_point=ghost_ps_vect.get_start(), + ) + + arc1 = Arc( + start_angle=PI, + angle=theta, + arc_center=point, + radius=0.5, + color=WHITE, + ) + arc2 = arc1.copy() + arc2.rotate(theta, about_point=point) + arc3 = arc1.copy() + arc3.rotate(PI, about_point=point) + arc1.set_color(BLUE) + + line_pair = VGroup(*[ + Line(point, point + LEFT).rotate( + angle, about_point=point + ) + for angle in [0, theta] + ]) + line_pair.set_stroke(width=0) + + ps_vect.suspend_updating() + self.play( + ShowCreation(arc1), + FadeIn(ghost_ps_vect) + ) + self.wait() + self.play( + Rotate(ps_vect, 2 * theta, about_point=point) + ) + self.begin_sliding() + ps_vect.resume_updating() + self.play(GrowFromPoint(arc2, point)) + self.wait(0.5) + self.end_sliding() + self.play( + TransformFromCopy(arc1, arc3, path_arc=-PI), + Rotate(line_pair, -PI, about_point=point), + UpdateFromAlphaFunc( + line_pair, lambda m, a: m.set_stroke( + width=there_and_back(a)**0.5 + ) + ), + ) + # Show light beam along trajectory + self.show_trajectory_beam_bounce() + self.wait() + + # TODO: Add labels for angles? + + self.play(FadeOut(VGroup( + self.ps_vect_circle, ghost_ps_vect, arc1 + ))) + + def show_horizontal_bounce(self): + self.slide_until( + lambda: self.ps_velocity_vector.get_vector()[1] > 0 + ) + point = self.ps_point.get_location() + theta = self.theta + arc1 = Arc( + start_angle=0, + angle=2 * theta, + radius=0.5, + arc_center=point, + ) + arc2 = arc1.copy() + arc2.rotate(PI - 2 * theta, about_point=point) + arcs = VGroup(arc1, arc2) + + self.slide(0.5) + self.play(LaggedStart( + FadeInFromLarge, arcs, + lag_ratio=0.75, + )) + self.show_trajectory_beam_bounce() + + def let_process_play_out(self): + self.slide(self.wait_time) + self.wait(10) # Just to be sure... + + # + def show_trajectory_beam_bounce(self, n_times=2): + # Show light beam along trajectory + beam = self.trajectory.copy() + beam.clear_updaters() + beam.set_stroke(YELLOW, 3) + for x in range(n_times): + self.play(ShowPassingFlash( + beam, + run_time=2, + rate_func=bezier([0, 0, 1, 1]) + )) + + def get_momentum_equation(self): + equation = TexMobject( + "m_1", "v_1", "+", "m_2", "v_2", + "=", "\\text{const.}", + tex_to_color_map={ + "m_1": BLUE, + "m_2": BLUE, + "v_1": RED, + "v_2": RED, + } + ) + equation.to_edge(UP, buff=MED_SMALL_BUFF) + return equation + + def get_dot_product(self, + m1="\\sqrt{m_1}", m2="\\sqrt{m_2}", + d1="dx/dt", d2="dy/dt"): + sqrty_m = Matrix([[m1], [m2]]) + deriv_array = Matrix([[d1], [d2]]) + for entry in sqrty_m.get_entries(): + if "sqrt" in entry.get_tex_string(): + entry[2:].set_color(BLUE) + for matrix in sqrty_m, deriv_array: + matrix.add_to_back(BackgroundRectangle(matrix)) + matrix.get_brackets().scale(0.9) + matrix.set_height(1.25) + dot = TexMobject("\\cdot") + rhs = TexMobject("= \\text{const.}") + dot_product = VGroup( + sqrty_m, dot, deriv_array, rhs + ) + dot_product.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + return dot_product + + +class JustTheProcessNew(PositionPhaseSpaceScene): + CONFIG = { + "block1_config": { + "mass": 16, + "velocity": -2 + }, + "wait_time": 10, + } + + def setup(self): + super().setup() + self.add( + self.floor, + self.wall, + self.blocks, + self.axes, + self.d1_eq_d2_line, + self.d2_eq_w2_line, + ) + + def construct(self): + self.slide(self.wait_time) diff --git a/old_projects/clacks/solution2/simple_scenes.py b/old_projects/clacks/solution2/simple_scenes.py new file mode 100644 index 00000000..efe8f656 --- /dev/null +++ b/old_projects/clacks/solution2/simple_scenes.py @@ -0,0 +1,841 @@ +from big_ol_pile_of_manim_imports import * +from old_projects.lost_lecture import ShowWord +from old_projects.clacks.solution2.mirror_scenes import ReflectWorldThroughMirrorNew +from old_projects.clacks.question import Thumbnail + + +class WrapperScene(Scene): + CONFIG = { + "title": "Title", + "shade_of_grey": "#333333" + } + + def construct(self): + title = TextMobject(self.title) + title.scale(1.5) + title.to_edge(UP) + big_rect = self.get_big_rect() + screen_rect = self.get_screen_rect() + screen_rect.next_to(title, DOWN) + + self.add(big_rect, screen_rect) + self.play( + FadeIn(big_rect), + FadeInFrom(title, DOWN), + FadeInFrom(screen_rect, UP), + ) + self.wait() + + def get_big_rect(self): + big_rect = FullScreenFadeRectangle() + big_rect.set_fill(self.shade_of_grey, 1) + return big_rect + + def get_screen_rect(self, height=6): + screen_rect = ScreenRectangle(height=height) + screen_rect.set_fill(BLACK, 1) + return screen_rect + + +class ComingUpWrapper(WrapperScene): + CONFIG = {"title": "Coming up..."} + + +class LastVideoWrapper(WrapperScene): + CONFIG = {"title": "Last time..."} + + +class LeftEdge(Scene): + CONFIG = { + "text": "Left edge", + "vect": LEFT, + } + + def construct(self): + words = TextMobject(self.text) + arrow = Vector(self.vect) + arrow.match_width(words) + arrow.next_to(words, DOWN) + + self.play( + FadeInFromDown(words), + GrowArrow(arrow) + ) + self.wait() + + +class RightEdge(LeftEdge): + CONFIG = { + "text": "Right edge", + "vect": RIGHT, + } + + +class NoteOnEnergyLostToSound(Scene): + def construct(self): + self.add(TextMobject( + "Yeah yeah, the clack sound\\\\" + "would require energy, but\\\\" + "don't let accuracy get in the\\\\" + "way of delight!", + alignment="", + )) + + +class DotProductVideoWrapper(WrapperScene): + CONFIG = {"title": "Dot product"} + + +class Rectangle(Scene): + def construct(self): + rect = ScreenRectangle(height=FRAME_HEIGHT - 0.25) + rect.set_stroke(WHITE, 6) + self.add(rect) + + +class ShowRectangleCreation(Scene): + def construct(self): + rect = ScreenRectangle(height=2) + rect.set_stroke(YELLOW, 6) + self.play(ShowCreation(rect)) + self.play(FadeOut(rect)) + + +class ShowDotProductMeaning(Scene): + def construct(self): + v_vect = Vector(2 * RIGHT, color=YELLOW) + w_vect = Vector(3 * RIGHT, color=PINK) + dot = Dot(color=RED) + dot.shift(DOWN) + + v_vect.angle_tracker = ValueTracker() + w_vect.angle_tracker = ValueTracker() + + def update_vect(vect): + target = vect.angle_tracker.get_value() + vect.rotate(target - vect.get_angle()) + vect.shift(dot.get_center() - vect.get_start()) + + v_vect.add_updater(update_vect) + w_vect.add_updater(update_vect) + + v_label = TexMobject("\\vec{\\textbf{v}}") + v_label.vect = v_vect + w_label = TexMobject("\\vec{\\textbf{w}}") + w_label.vect = w_vect + for label in v_label, w_label: + label.match_color(label.vect) + label.set_stroke(BLACK, 5, background=True) + + def update_label(label): + target = np.array(label.vect.get_end()) + target += 0.25 * normalize(label.vect.get_vector()) + label.move_to(target) + + v_label.add_updater(update_label) + w_label.add_updater(update_label) + + title = TexMobject( + "\\vec{\\textbf{w}}", + "\\cdot", + "\\vec{\\textbf{v}}", + "=", + "||", "\\vec{\\textbf{w}}", "||", + "\\cdot", + "||", "\\vec{\\textbf{v}}", "||", + "\\cdot", + "\\cos(\\theta)" + ) + title.set_color_by_tex_to_color_map({ + "textbf{v}": v_vect.get_color(), + "textbf{w}": w_vect.get_color(), + }) + title.to_edge(UP) + + def get_w_line(): + center = dot.get_center() + direction = w_vect.get_vector() + return Line( + center - 3 * direction, + center + 3 * direction, + stroke_color=LIGHT_GREY, + stroke_width=1, + ) + w_line = updating_mobject_from_func(get_w_line) + + def get_proj_v(): + center = dot.get_center() + v = v_vect.get_vector() + w = w_vect.get_vector() + w_unit = normalize(w) + result = Vector(np.dot(v, w_unit) * w_unit) + result.set_fill(v_vect.get_color(), 0.5) + result.shift(center - result.get_start()) + return result + proj_v = updating_mobject_from_func(get_proj_v) + + def get_proj_line(): + return DashedLine( + v_vect.get_end(), + proj_v.get_end(), + stroke_width=1, + dashed_segment_length=0.025, + ) + proj_line = updating_mobject_from_func(get_proj_line) + + template_line = Line(LEFT, RIGHT) + + def get_vect_brace(vect): + brace = Brace(template_line, UP, buff=SMALL_BUFF) + brace.set_width(vect.get_length(), stretch=True) + angle = vect.get_angle() % TAU + if angle < PI: + angle += PI + brace.rotate(angle, about_point=ORIGIN) + brace.shift(vect.get_center()) + return brace + w_brace = updating_mobject_from_func( + lambda: get_vect_brace(w_vect) + ) + proj_v_brace = updating_mobject_from_func( + lambda: get_vect_brace(proj_v) + ) + + def get_arc(): + center = dot.get_center() + a1 = w_vect.get_angle() + a2 = v_vect.get_angle() + arc = Arc( + start_angle=a1, + angle=a2 - a1, + radius=0.5, + arc_center=center, + ) + theta = TexMobject("\\theta") + p = arc.point_from_proportion(0.5) + theta.move_to( + center + 1.5 * (p - center) + ) + return VGroup(arc, theta) + arc = updating_mobject_from_func(get_arc) + + self.add( + title[:3], + w_vect, v_vect, dot, + w_label, v_label, + ) + self.play( + v_vect.angle_tracker.set_value, 170 * DEGREES, + w_vect.angle_tracker.set_value, 45 * DEGREES, + run_time=2, + ) + self.wait() + w_brace.update() + self.play( + GrowFromCenter(w_brace), + Write(title[3:7]) + ) + + self.add(w_line, w_vect, w_label, dot) + self.play(ShowCreation(w_line)) + proj_v.update() + self.play( + ShowCreation(proj_line), + TransformFromCopy(v_vect, proj_v), + ) + self.add(proj_v, proj_line, dot) + proj_v_brace.update() + self.play( + GrowFromCenter(proj_v_brace), + FadeInFromDown(title[7:]) + ) + arc.update() + self.play(Write(arc)) + self.wait() + + for angle in [135, 225, 270, 90, 150]: + self.play( + v_vect.angle_tracker.set_value, angle * DEGREES, + run_time=2 + ) + self.wait() + + +class FinalComment(Scene): + def construct(self): + self.add(TextMobject( + "Thoughts on what ending should go here?\\\\" + "See the Patreon post." + )) + + +class FourtyFiveDegreeLine(Scene): + CONFIG = { + "angle": 45 * DEGREES, + "label_config": { + "num_decimal_places": 0, + "unit": "^\\circ", + "label_height": 0.3, + }, + "degrees": True + } + + def construct(self): + angle = self.angle + arc = Arc(angle, radius=1) + label = DecimalNumber(0, **self.label_config) + label.set_height(self.label_config["label_height"]) + label.next_to(arc, RIGHT) + label.shift(0.5 * SMALL_BUFF * UP) + line1 = Line(ORIGIN, 3 * RIGHT) + line2 = line1.copy() + + if self.degrees: + target_value = int(angle / DEGREES) + else: + target_value = angle + + self.add(line1, label) + self.play( + ChangeDecimalToValue(label, target_value), + ShowCreation(arc), + Rotate(line2, angle, about_point=ORIGIN) + ) + self.wait() + + +class ArctanSqrtPoint1Angle(FourtyFiveDegreeLine): + CONFIG = { + "angle": np.arctan(np.sqrt(0.1)), + } + + +class AskAboutAddingThetaToItself(Scene): + CONFIG = { + "theta": np.arctan(0.25), + "wait_time": 0.25, + "wedge_radius": 3, + "theta_symbol_scale_val": 0.5, + "number_height": 0.2, + } + + def construct(self): + theta = self.theta + groups = self.get_groups(theta) + horizon = self.get_horizon() + counter = ValueTracker(0) + dynamic_ineq = self.get_dynamic_inequality(counter) + semicircle = self.get_semicircle() + + self.add(horizon) + self.add(dynamic_ineq) + + for n in range(len(groups)): + counter.set_value(n + 1) + if n < len(groups) - 1: + groups[n][-1].set_color(YELLOW) + if n > 0: + groups[n - 1][-1].set_color(WHITE) + self.add(groups[:n + 1]) + self.add_sound("pen_click", gain=-20) + self.wait(self.wait_time) + self.wait(0.5) + + counter.set_value(counter.get_value() - 1) + self.remove(groups[-1]) + self.add_sound("pen_click", gain=-20) + self.wait() + + self.play(ShowCreation(semicircle)) + self.play(FadeOut(semicircle)) + + self.wait(3) + + def get_group(self, theta): + # Define group + wedge_radius = self.wedge_radius + wedge = VGroup( + Line(ORIGIN, wedge_radius * RIGHT), + Line(ORIGIN, wedge_radius * RIGHT).rotate( + theta, about_point=ORIGIN + ), + ) + wedge.set_stroke((WHITE, GREY), 2) + arc = Arc(theta, radius=1) + theta_symbol = TexMobject("\\theta") + tssv = self.theta_symbol_scale_val + theta_symbol.scale(tssv) + theta_symbol.next_to(arc, RIGHT, tssv / 2) + theta_symbol.shift(tssv * SMALL_BUFF * UP) + + return VGroup(wedge, arc, theta_symbol) + + def get_groups(self, theta): + group = self.get_group(theta) + angles = [k * theta for k in range(int(PI / theta) + 1)] + groups = VGroup(*[ + group.copy().rotate(angle, about_point=ORIGIN) + for angle in angles + ]) + # colors = it.cycle([BLUE_D, BLUE_B, BLUE_C, GREY_BROWN]) + colors = it.cycle([BLUE_D, GREY_BROWN]) + for n, angle, group, color in zip(it.count(1), angles, groups, colors): + wedge, arc, symbol = group + symbol.rotate(-angle) + arc.set_color(color) + number = Integer(n) + number.set_height(self.number_height) + number.move_to(center_of_mass([ + wedge[0].get_end(), + wedge[1].get_end(), + ])) + group.add(number) + groups[-1][-1].set_color(RED) + + return groups + + def get_horizon(self): + horizon = DashedLine(5 * LEFT, 5 * RIGHT) + horizon.set_stroke(WHITE, 1) + return horizon + + def get_semicircle(self): + return Arc( + start_angle=0, + angle=PI, + radius=self.wedge_radius / 2, + color=YELLOW, + stroke_width=4, + ) + + def get_inequality(self): + ineq = TexMobject( + "N", "\\cdot", "\\theta", "<", + "\\pi", "=", "3.1415926\\dots" + ) + N = ineq.get_part_by_tex("N") + self.pi_symbol = ineq.get_part_by_tex("\\pi") + N.set_color(YELLOW) + # ineq[-3:].set_color(BLUE) + + brace = Brace(N, UP, buff=SMALL_BUFF) + text = brace.get_text("Maximum", buff=SMALL_BUFF) + group = VGroup(ineq, brace, text) + group.next_to(ORIGIN, DOWN, MED_LARGE_BUFF) + return group + + def get_dynamic_inequality(self, counter): + multiple = Integer(0) + dot = TexMobject("\\cdot") + theta_tex = TexMobject("({:.2f})".format(self.theta)) + eq = TexMobject("=") + value = DecimalNumber(0) + ineq = TexMobject("<") + pi = TexMobject("\\pi", "=", "3.1415926\\dots") + # pi.set_color(BLUE) + group = VGroup( + multiple, dot, theta_tex, + eq, value, + ineq, pi + ) + group.arrange_submobjects(RIGHT, buff=0.2) + group.next_to(ORIGIN, DOWN, buff=LARGE_BUFF) + theta_brace = Brace(group[2], DOWN, buff=SMALL_BUFF) + theta_symbol = theta_brace.get_tex("\\theta") + group.add(theta_brace, theta_symbol) + # group.align_to(self.pi_symbol, RIGHT) + + def get_count(): + return int(counter.get_value()) + + def get_product(): + return get_count() * self.theta + + def is_greater_than_pi(): + return get_product() > PI + + def get_color(): + return RED if is_greater_than_pi() else YELLOW + + def get_ineq(): + result = TexMobject( + ">" if is_greater_than_pi() else "<" + ) + result.set_color(get_color()) + result.move_to(ineq) + return result + dynamic_ineq = updating_mobject_from_func(get_ineq) + group.remove(ineq) + group.add(dynamic_ineq) + + multiple.add_updater(lambda m: m.set_value(get_count())) + multiple.add_updater(lambda m: m.next_to(dot, LEFT, 0.2)) + multiple.add_updater(lambda m: m.set_color(get_color())) + value.add_updater(lambda m: m.set_value(get_product())) + + return group + + +class AskAboutAddingThetaToItselfThetaPoint1(AskAboutAddingThetaToItself): + CONFIG = { + "theta": 0.1, + "wait_time": 0.1, + "theta_symbol_scale_val": 0.25, + "number_height": 0.15, + } + + +class AskAboutAddingThetaToItselfThetaPoint2(AskAboutAddingThetaToItself): + CONFIG = { + "theta": 0.2, + "wait_time": 0.1, + } + + +class FinalFormula(Scene): + def construct(self): + text = TextMobject("Final answer: ") + t2c_map = { + "\\theta": BLUE, + "m_1": GREEN, + "m_2": RED, + } + + formula = TexMobject( + "\\left\\lfloor", + "{\\pi", "\\over", "\\theta}", + "\\right\\rfloor" + ) + formula.set_color_by_tex_to_color_map(t2c_map) + group = VGroup(text, formula) + group.arrange_submobjects(RIGHT) + group.scale(1.5) + group.to_edge(UP) + + self.play(Write(text)) + self.play(FadeInFrom(formula)) + self.play(ShowCreationThenFadeAround(formula)) + self.wait() + + theta_eq = TexMobject( + "\\theta", "=", "\\arctan", "\\left(", + "\\sqrt", + "{{m_2", "\\over", "m_1}}", + "\\right)" + ) + theta_eq.set_color_by_tex_to_color_map(t2c_map) + theta_eq.scale(1.5) + theta_eq.next_to(group, DOWN, MED_LARGE_BUFF) + + self.play(TransformFromCopy( + formula.get_part_by_tex("\\theta"), + theta_eq.get_part_by_tex("\\theta"), + )) + self.play(Write(theta_eq[1:])) + self.wait() + + +class ReviewWrapper(WrapperScene): + CONFIG = {"title": "To review:"} + + +class SurprisedRandy(Scene): + def construct(self): + randy = Randolph() + self.play(randy.change, "surprised", 3 * UR) + self.play(Blink(randy)) + self.play(randy.change, "confused") + self.play(Blink(randy)) + self.wait() + + +class TwoSolutionsWrapper(WrapperScene): + def construct(self): + big_rect = self.get_big_rect() + screen_rects = VGroup(*[ + self.get_screen_rect(height=3) + for x in range(2) + ]) + screen_rects.arrange_submobjects(RIGHT, buff=LARGE_BUFF) + title = TextMobject("Two solutions") + title.scale(1.5) + title.to_edge(UP) + screen_rects.next_to(title, DOWN, LARGE_BUFF) + + # pi creatures + pis = VGroup( + Randolph(color=BLUE_D), + Randolph(color=BLUE_E), + Randolph(color=BLUE_B), + Mortimer().scale(1.2) + ) + pis.set_height(2) + pis.arrange_submobjects(RIGHT, buff=MED_LARGE_BUFF) + pis.to_edge(DOWN, buff=SMALL_BUFF) + + self.add(big_rect, title, pis) + self.play( + LaggedStart( + ShowCreation, screen_rects.copy().set_fill(opacity=0), + lag_ratio=0.8 + ), + LaggedStart( + FadeIn, screen_rects, + lag_ratio=0.8 + ), + LaggedStart( + ApplyMethod, pis, + lambda pi: (pi.change, "pondering", screen_rects[0]) + ), + ) + self.play(Blink(random.choice(pis))) + self.play(LaggedStart( + ApplyMethod, pis, + lambda pi: (pi.change, "thinking", screen_rects[1]) + )) + self.play(Blink(random.choice(pis))) + self.wait() + + +class FinalQuote(Scene): + def construct(self): + quote_text = """ + A change of perspective\\\\ + is worth 80 IQ points. + """ + quote_parts = [s for s in quote_text.split(" ") if s] + quote = TextMobject( + *quote_parts, + ) + quote.scale(1.2) + quote.shift(2 * RIGHT + UP) + + image = ImageMobject("AlanKay") + image.set_height(6) + image.to_corner(UL) + image.shift(2 * LEFT + 0.5 * UP) + name = TextMobject("Alan Kay") + name.scale(1.5) + name.next_to(image, DOWN) + name.shift_onto_screen() + + self.play( + FadeInFromDown(image), + Write(name), + ) + self.wait() + + for word in quote: + self.play(ShowWord(word)) + self.wait(0.005 * len(word)**1.5) + self.wait() + + +class EndScreen(PatreonEndScreen): + CONFIG = { + "specific_patrons": [ + "1stViewMaths", + "Adam Kozak", + "Adrian Robinson", + "Alexis Olson", + "Ali Yahya", + "Andreas Benjamin Brössel", + "Andrew Busey", + "Ankalagon", + "Antonio Juarez", + "Arjun Chakroborty", + "Art Ianuzzi", + "Arthur Zey", + "Awoo", + "Bernd Sing", + "Bob Sanderson", + "Boris Veselinovich", + "Brian Staroselsky", + "Britt Selvitelle", + "Burt Humburg", + "Chad Hurst", + "Charles Southerland", + "Chris Connett", + "Christian Kaiser", + "Clark Gaebel", + "Cooper Jones", + "D. Sivakumar", + "Danger Dai", + "Dave B", + "Dave Kester", + "dave nicponski", + "David Clark", + "David Gow", + "Delton Ding", + "Devarsh Desai", + "eaglle", + "emptymachine", + "Eric Younge", + "Eryq Ouithaqueue", + "Evan Phillips", + "Federico Lebron", + "Florian Chudigiewitsch", + "Giovanni Filippi", + "Graham", + "Hal Hildebrand", + "Hitoshi Yamauchi", + "J", + "j eduardo perez", + "Jacob Magnuson", + "Jameel Syed", + "James Hughes", + "Jan Pijpers", + "Jason Hise", + "Jeff Linse", + "Jeff Straathof", + "John Griffith", + "John Haley", + "John Shaughnessy", + "John V Wertheim", + "Jonathan Eppele", + "Jonathan Wilson", + "Jordan Scales", + "Joseph John Cox", + "Joseph Kelly", + "Juan Benet", + "Kai-Siang Ang", + "Kanan Gill", + "Kaustuv DeBiswas", + "L0j1k", + "Lee Redden", + "Linh Tran", + "Luc Ritchie", + "Ludwig Schubert", + "Lukas -krtek.net- Novy", + "Lukas Biewald", + "Magister Mugit", + "Magnus Dahlström", + "Magnus Lysfjord", + "Mark B Bahu", + "Mark Heising", + "Mathew Bramson", + "Mathias Jansson", + "Matt Langford", + "Matt Roveto", + "Matt Russell", + "Matthew Cocke", + "Mauricio Collares", + "Michael Faust", + "Michael Hardel", + "Mike Coleman", + "Mustafa Mahdi", + "Márton Vaitkus", + "Nathan Jessurun", + "Nero Li", + "Omar Zrien", + "Owen Campbell-Moore", + "Peter Ehrnstrom", + "Peter Mcinerney", + "Quantopian", + "Randy C. Will", + "Richard Barthel", + "Richard Burgmann", + "Richard Comish", + "Ripta Pasay", + "Rish Kundalia", + "Robert Teed", + "Roobie", + "Roy Larson", + "Ryan Atallah", + "Ryan Williams", + "Samuel D. Judge", + "Scott Gray", + "Scott Walter, Ph.D.", + "Sindre Reino Trosterud", + "soekul", + "Solara570", + "Song Gao", + "Stevie Metke", + "Ted Suzman", + "Tihan Seale", + "Valeriy Skobelev", + "Vassili Philippov", + "Xavier Bernard", + "Yana Chernobilsky", + "Yaw Etse", + "YinYangBalance.Asia", + "Yu Jun", + "Zach Cardwell", + ], + } + + +class ClacksSolution2Thumbnail(Scene): + def construct(self): + self.add_scene1() + self.add_scene2() + + arrow = TexMobject("\\Updownarrow") + arrow.set_height(2) + arrow.set_color(YELLOW) + arrow.set_stroke(Color("red"), 2, background=True) + self.add(arrow) + + def add_scene1(self): + scene1 = Thumbnail( + sliding_blocks_config={ + "block1_config": { + "label_text": "$100^{d}$ kg", + "distance": 8, + }, + } + ) + VGroup(*scene1.mobjects).shift(0.9 * DOWN) + scene1.remove(scene1.question) + self.add(*scene1.mobjects) + + rect = FullScreenFadeRectangle(fill_opacity=1) + rect.shift(FRAME_HEIGHT * UP / 2) + self.add(rect) + + def add_scene2(self): + scene2 = ReflectWorldThroughMirrorNew( + skip_animations=True, + file_writer_config={ + "write_to_movie": False, + }, + end_at_animation_number=18, + center=1.5 * UP, + ) + worlds = VGroup(scene2.world, *scene2.reflected_worlds) + mirrors = VGroup(*[rw[1] for rw in worlds]) + mirrors.set_stroke(width=5) + randys = VGroup(*[rw[-1] for rw in worlds]) + triangles = VGroup(*[rw[0] for rw in worlds]) + trajectories = VGroup( + scene2.trajectory, + *scene2.reflected_trajectories + ) + trajectories.set_stroke(YELLOW, 1) + + beams1, beams2 = [ + scene2.get_shooting_beam_anims( + path, + max_stroke_width=20, + max_time_width=1, + num_flashes=50, + ) + for path in [ + scene2.trajectory, + scene2.ghost_trajectory, + ] + ] + beams = beams1 + beams2 + beams = beams2 + flashes = VGroup() + for beam in beams: + beam.update(0.5) + flashes.add(beam.mobject) + + dot = Dot(color=YELLOW, radius=0.1) + dot.move_to(flashes[0].get_left()) + flashes.add(dot) + + self.add(mirrors, triangles, randys) + self.add(trajectories[0].set_stroke(width=3)) + self.add(flashes) diff --git a/active_projects/clacks/solution2/wordy_scenes.py b/old_projects/clacks/solution2/wordy_scenes.py similarity index 65% rename from active_projects/clacks/solution2/wordy_scenes.py rename to old_projects/clacks/solution2/wordy_scenes.py index c2f3a61a..2be0cce2 100644 --- a/active_projects/clacks/solution2/wordy_scenes.py +++ b/old_projects/clacks/solution2/wordy_scenes.py @@ -1,4 +1,5 @@ from big_ol_pile_of_manim_imports import * +from old_projects.clacks.solution2.position_phase_space import ShowMomentumConservation class ConnectionToOptics(Scene): @@ -145,7 +146,7 @@ class ConnectionToOptics(Scene): title = VGroup(*map(TextMobject, [ "Angle of\\\\Incidence", "=", - "Angle of\\\\Refraction", + "Angle of\\\\Reflection", ])).arrange_submobjects(RIGHT) title.set_color(YELLOW) h_line = Line(LEFT, RIGHT) @@ -203,3 +204,115 @@ class ConnectionToOptics(Scene): class ConnectionToOpticsTransparent(ConnectionToOptics): pass + + +class RearrangeMomentumEquation(ShowMomentumConservation): + def setup(self): + pass # Don't build all the things + + def construct(self): + self.add(FullScreenFadeRectangle( + fill_color=BLACK, + fill_opacity=0.95, + )) + self.show_initial_dot_product() + self.show_with_x_and_y() + + def show_initial_dot_product(self): + equation = self.get_momentum_equation() + dot_product = self.get_dot_product( + "m_1", "m_2", "v_1", "v_2" + ) + dot_product.next_to(equation, DOWN, LARGE_BUFF) + m_array, dot, v_array, rhs = dot_product + m_array.get_entries().set_color(BLUE) + v_array.get_entries().set_color(RED) + + self.add(equation) + self.play(FadeInFromDown(VGroup( + m_array.get_brackets(), dot, + v_array.get_brackets(), rhs, + ))) + self.play(TransformFromCopy( + equation.get_parts_by_tex("m_"), + m_array.get_entries(), + )) + self.play(TransformFromCopy( + equation.get_parts_by_tex("v_"), + v_array.get_entries(), + )) + self.wait() + + self.simple_dot_product = dot_product + self.momentum_equation = equation + + def show_with_x_and_y(self): + simple_dot_product = self.simple_dot_product + momentum_equation = self.momentum_equation + + new_equation = TexMobject( + "\\sqrt{m_1}", + "\\left(", "\\sqrt{m_1}", "v_1", "\\right)", + "+", "\\sqrt{m_2}", + "\\left(", "\\sqrt{m_2}", "v_2", "\\right)", + "=", "\\text{const.}", + ) + new_equation.set_color_by_tex_to_color_map({ + "m_": BLUE, + "v_": RED, + }) + new_equation.next_to(momentum_equation, DOWN, MED_LARGE_BUFF) + + x_term = new_equation[1:5] + y_term = new_equation[7:11] + x_brace = Brace(x_term, DOWN) + y_brace = Brace(y_term, DOWN) + dx_dt = x_brace.get_tex("dx / dt") + dy_dt = y_brace.get_tex("dy / dt") + + new_eq_group = VGroup( + new_equation, x_brace, y_brace, dx_dt, dy_dt + ) + new_eq_group.generate_target() + + new_dot_product = self.get_dot_product() + m_array, dot, d_array, rhs = new_dot_product + new_dot_product.next_to(momentum_equation, DOWN) + new_eq_group.target.next_to(new_dot_product, DOWN, LARGE_BUFF) + + self.play( + FadeInFrom(new_equation, UP), + simple_dot_product.to_edge, DOWN, LARGE_BUFF, + ) + self.wait() + self.play( + GrowFromCenter(x_brace), + GrowFromCenter(y_brace), + FadeInFrom(dx_dt, UP), + FadeInFrom(dy_dt, UP), + ) + self.wait() + self.play( + FadeIn(VGroup( + m_array.get_brackets(), dot, + d_array.get_brackets(), rhs + )), + MoveToTarget(new_eq_group) + ) + self.play(TransformFromCopy( + VGroup( + VGroup(new_equation[0]), + VGroup(new_equation[6]), + ), + m_array.get_entries(), + )) + self.play(TransformFromCopy( + VGroup(dx_dt, dy_dt), + d_array.get_entries(), + )) + self.wait() + + +class NewSceneName(Scene): + def construct(self): + pass diff --git a/stage_scenes.py b/stage_scenes.py index 6fff1586..f993081e 100644 --- a/stage_scenes.py +++ b/stage_scenes.py @@ -5,14 +5,17 @@ import sys import importlib from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG -from manimlib.constants import PRODUCTION_QUALITY_FRAME_DURATION +from manimlib.constants import VIDEO_DIR from manimlib.config import get_module from manimlib.extract_scene import is_child_scene -from manimlib.utils.file_ops import get_movie_output_directory def get_sorted_scene_classes(module_name): module = get_module(module_name) + if hasattr(module, "ALL_SCENE_CLASSES"): + return module.ALL_SCENE_CLASSES + # Otherwise, deduce from the order in which + # they're defined in a file importlib.import_module(module.__name__) line_to_scene = {} name_scene_list = inspect.getmembers( @@ -30,17 +33,19 @@ def get_sorted_scene_classes(module_name): ] -def stage_animations(module_name): +def stage_scenes(module_name): scene_classes = get_sorted_scene_classes(module_name) if len(scene_classes) == 0: print("There are no rendered animations from this module") return - output_directory_kwargs = { - "camera_config": PRODUCTION_QUALITY_CAMERA_CONFIG, - } - animation_dir = get_movie_output_directory( - scene_classes[0], **output_directory_kwargs + # output_directory_kwargs = { + # "camera_config": PRODUCTION_QUALITY_CAMERA_CONFIG, + # } + # TODO, fix this + animation_dir = os.path.join( + VIDEO_DIR, "clacks_solution2", "1440p60" ) + # files = os.listdir(animation_dir) sorted_files = [] for scene_class in scene_classes: @@ -92,4 +97,4 @@ if __name__ == "__main__": if len(sys.argv) < 2: raise Exception("No module given.") module_name = sys.argv[1] - stage_animations(module_name) + stage_scenes(module_name)