Files

1022 lines
32 KiB
Python

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()