from manimlib.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(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 always_redraw(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(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 update_mobjects(self, dt): super().update_mobjects(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=linear, **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 = always_redraw( 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(LaggedStartMap( 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(LaggedStartMap(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), LaggedStartMap(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 = always_redraw(get_dl_arc) def get_dr_arc(): return Arc( start_angle=0, angle=dr_wire.get_angle() - PI, **arc_config, ) dr_arc = always_redraw(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()