mirror of
https://github.com/3b1b/videos.git
synced 2025-09-18 21:38:53 +00:00
4243 lines
148 KiB
Python
4243 lines
148 KiB
Python
from __future__ import annotations
|
|
from manim_imports_ext import *
|
|
|
|
|
|
def hsl_to_rgb(hsl):
|
|
"""
|
|
Convert an array of HSL values to RGB.
|
|
|
|
Args:
|
|
hsl (np.ndarray): A numpy array of shape (n, 3), where each row represents an HSL value
|
|
(Hue [0, 1), Saturation [0, 1], Lightness [0, 1]).
|
|
|
|
Returns:
|
|
np.ndarray: An array of shape (n, 3), containing RGB values in the range [0, 1].
|
|
"""
|
|
h = hsl[:, 0]
|
|
s = hsl[:, 1]
|
|
l = hsl[:, 2]
|
|
|
|
def hue_to_rgb(p, q, t):
|
|
t = np.where(t < 0, t + 1, np.where(t > 1, t - 1, t))
|
|
return np.where(t < 1/6, p + (q - p) * 6 * t,
|
|
np.where(t < 1/2, q,
|
|
np.where(t < 2/3, p + (q - p) * (2/3 - t) * 6, p
|
|
)))
|
|
|
|
q = np.where(l < 0.5, l * (1 + s), l + s - l * s)
|
|
p = 2 * l - q
|
|
|
|
r = hue_to_rgb(p, q, h + 1 / 3)
|
|
g = hue_to_rgb(p, q, h)
|
|
b = hue_to_rgb(p, q, h - 1 / 3)
|
|
|
|
rgb = np.stack([r, g, b], axis=1)
|
|
return rgb
|
|
|
|
|
|
class LightWaveSlice(Mobject):
|
|
shader_folder: str = str(Path(Path(__file__).parent, "diffraction_shader"))
|
|
data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
|
|
('point', np.float32, (3,)),
|
|
]
|
|
render_primitive: int = moderngl.TRIANGLE_STRIP
|
|
|
|
def __init__(
|
|
self,
|
|
point_sources: DotCloud,
|
|
shape: tuple[float, float] = (8.0, 8.0),
|
|
color: ManimColor = BLUE_D,
|
|
opacity: float = 1.0,
|
|
frequency: float = 1.0,
|
|
wave_number: float = 1.0,
|
|
max_amp: Optional[float] = None,
|
|
decay_factor: float = 0.5,
|
|
show_intensity: bool = False,
|
|
**kwargs
|
|
):
|
|
self.shape = shape
|
|
self.point_sources = point_sources
|
|
self._is_paused = False
|
|
super().__init__(**kwargs)
|
|
|
|
if max_amp is None:
|
|
max_amp = point_sources.get_num_points()
|
|
self.set_uniforms(dict(
|
|
frequency=frequency,
|
|
wave_number=wave_number,
|
|
max_amp=max_amp,
|
|
time=0,
|
|
decay_factor=decay_factor,
|
|
show_intensity=float(show_intensity),
|
|
time_rate=1.0,
|
|
))
|
|
self.set_color(color, opacity)
|
|
|
|
self.add_updater(lambda m, dt: m.increment_time(dt))
|
|
self.always.sync_points()
|
|
self.apply_depth_test()
|
|
|
|
def init_data(self) -> None:
|
|
super().init_data(length=4)
|
|
self.data["point"][:] = [UL, DL, UR, DR]
|
|
|
|
def init_points(self) -> None:
|
|
self.set_shape(*self.shape)
|
|
|
|
def set_color(
|
|
self,
|
|
color: ManimColor | Iterable[ManimColor] | None,
|
|
opacity: float | Iterable[float] | None = None,
|
|
recurse=False,
|
|
) -> Self:
|
|
if color is not None:
|
|
self.set_uniform(color=color_to_rgb(color))
|
|
if opacity is not None:
|
|
self.set_uniform(opacity=opacity)
|
|
return self
|
|
|
|
def set_opacity(self, opacity: float, recurse=False):
|
|
self.set_uniform(opacity=opacity)
|
|
return self
|
|
|
|
def set_wave_number(self, wave_number: float):
|
|
self.set_uniform(wave_number=wave_number)
|
|
return self
|
|
|
|
def set_frequency(self, frequency: float):
|
|
self.set_uniform(frequency=frequency)
|
|
return self
|
|
|
|
def set_max_amp(self, max_amp: float):
|
|
self.set_uniform(max_amp=max_amp)
|
|
return self
|
|
|
|
def set_decay_factor(self, decay_factor: float):
|
|
self.set_uniform(decay_factor=decay_factor)
|
|
return self
|
|
|
|
def set_time_rate(self, time_rate: float):
|
|
self.set_uniform(time_rate=time_rate)
|
|
return self
|
|
|
|
def set_sources(self, point_sources: DotCloud):
|
|
self.point_sources = point_sources
|
|
return self
|
|
|
|
def sync_points(self):
|
|
sources: DotCloud = self.point_sources
|
|
for n, point in enumerate(sources.get_points()):
|
|
self.set_uniform(**{f"point_source{n}": point})
|
|
self.set_uniform(n_sources=sources.get_num_points())
|
|
return self
|
|
|
|
def increment_time(self, dt):
|
|
self.uniforms["time"] += self.uniforms["time_rate"] * dt
|
|
return self
|
|
|
|
def show_intensity(self, show: bool = True):
|
|
self.set_uniform(show_intensity=float(show))
|
|
|
|
def pause(self):
|
|
self.set_uniform(time_rate=0)
|
|
return self
|
|
|
|
def unpause(self):
|
|
self.set_uniform(time_rate=1)
|
|
return self
|
|
|
|
def interpolate(
|
|
self,
|
|
wave1: LightWaveSlice,
|
|
wave2: LightWaveSlice,
|
|
alpha: float,
|
|
path_func: Callable[[np.ndarray, np.ndarray, float], np.ndarray] = straight_path
|
|
) -> Self:
|
|
self.locked_uniform_keys.add("time")
|
|
super().interpolate(wave1, wave2, alpha, path_func)
|
|
|
|
def wave_func(self, points):
|
|
time = self.uniforms["time"]
|
|
wave_number = self.uniforms["wave_number"]
|
|
frequency = self.uniforms["frequency"]
|
|
decay_factor = self.uniforms["decay_factor"]
|
|
|
|
values = np.zeros(len(points))
|
|
for source_point in self.point_sources.get_points():
|
|
dists = np.linalg.norm(points - source_point, axis=1)
|
|
values += np.cos(TAU * (wave_number * dists - frequency * time)) * (dists + 1)**(-decay_factor)
|
|
return values
|
|
|
|
|
|
class LightIntensity(LightWaveSlice):
|
|
def __init__(
|
|
self,
|
|
*args,
|
|
color: ManimColor = BLUE,
|
|
show_intensity: bool = True,
|
|
**kwargs
|
|
):
|
|
super().__init__(*args, color=color, show_intensity=show_intensity, **kwargs)
|
|
|
|
|
|
# Scenes
|
|
|
|
|
|
class LightFieldAroundScene(InteractiveScene):
|
|
def construct(self):
|
|
# Add scene
|
|
frame = self.frame
|
|
folder = "/Users/grant/3Blue1Brown Dropbox/3Blue1Brown/videos/2024/holograms/Paul Animations/LightFieldDraft"
|
|
scene, scene_top, lamp = group = Group(
|
|
ImageMobject(os.path.join(folder, "LightFieldScene")),
|
|
ImageMobject(os.path.join(folder, "TopHalfCutoff")),
|
|
ImageMobject(os.path.join(folder, "Lamp")),
|
|
)
|
|
group.set_height(7)
|
|
group.to_edge(RIGHT)
|
|
|
|
light_point = scene.get_corner(UL) + 1.6 * RIGHT + 0.8 * DOWN
|
|
scene_point = scene.get_center() + 0.8 * RIGHT + 0.4 * DOWN
|
|
|
|
frame.reorient(0, 0, 0, (3.44, -0.2, 0.0), 4.78)
|
|
self.add(scene)
|
|
self.play(FadeIn(scene, 0.25 * UP))
|
|
self.wait()
|
|
|
|
# Add light
|
|
line = Line(light_point, scene_point)
|
|
n_dots = 50
|
|
light = Group(
|
|
GlowDot(line.pfp(a), color=WHITE, radius=interpolate(0.5, 4, a), opacity=3 / n_dots)
|
|
for a in np.linspace(0, 1, n_dots)
|
|
)
|
|
self.play(
|
|
FadeIn(lamp, time_span=(1, 3)),
|
|
FadeIn(light, lag_ratio=0.2, time_span=(1, 3)),
|
|
frame.animate.reorient(0, 0, 0, (3.64, 0.68, 0.0), 5.30),
|
|
run_time=2,
|
|
)
|
|
self.wait()
|
|
|
|
# Set up wave
|
|
sources = DotCloud([
|
|
(2.42, -0.27, 0.0),
|
|
(1.96, -0.14, 0.0),
|
|
(2.11, 0.05, 0.0),
|
|
(2.51, 0.16, 0.0),
|
|
(2.75, 0.07, 0.0),
|
|
(3.19, 0.41, 0.0),
|
|
(3.48, 0.39, 0.0),
|
|
(4.81, 0.28, 0.0),
|
|
(5.1, 0.22, 0.0),
|
|
(5.53, -0.04, 0.0),
|
|
(5.8, -0.18, 0.0),
|
|
(4.87, -0.58, 0.0),
|
|
(4.71, -0.68, 0.0),
|
|
(4.28, -0.61, 0.0),
|
|
(3.92, -0.51, 0.0),
|
|
(4.18, -0.27, 0.0),
|
|
(3.8, 0.09, 0.0),
|
|
(3.55, 0.23, 0.0),
|
|
(2.43, 0.7, 0.0),
|
|
(2.43, 0.7, 0.0),
|
|
(3.71, 0.51, 0.0),
|
|
(3.94, -0.49, 0.0),
|
|
(1.9, -0.3, 0.0),
|
|
(2.61, 0.06, 0.0),
|
|
(2.92, 0.23, 0.0),
|
|
(3.25, 0.38, 0.0),
|
|
])
|
|
wave = LightWaveSlice(sources)
|
|
wave.set_max_amp(math.sqrt(30))
|
|
wave.set_shape(50, 100)
|
|
wave.rotate(70 * DEGREES, LEFT)
|
|
wave.set_wave_number(4)
|
|
wave.set_frequency(1)
|
|
|
|
n_perp_waves = 10
|
|
perp_waves = Group(wave.copy().rotate(PI / 2, RIGHT).shift(z * OUT) for z in np.linspace(-1, 1, n_perp_waves))
|
|
perp_waves.set_opacity(0.1)
|
|
|
|
self.add(scene, wave, perp_waves, scene_top, lamp, light)
|
|
self.play(
|
|
FadeIn(wave),
|
|
FadeIn(perp_waves),
|
|
light.animate.set_opacity(2 / n_dots),
|
|
scene.animate.set_opacity(0.5),
|
|
run_time=2
|
|
)
|
|
|
|
# Slow zoom out
|
|
self.play(
|
|
frame.animate.to_default_state(),
|
|
run_time=12
|
|
)
|
|
|
|
# Linger
|
|
self.wait(12)
|
|
|
|
# Reflection
|
|
if False:
|
|
self.remove(light)
|
|
self.remove(lamp)
|
|
self.frame.set_theta(-10 * DEGREES).set_y(0.5)
|
|
scene_top.set_opacity(0.5)
|
|
self.wait(12)
|
|
|
|
# Show an observer
|
|
eye_dot = GlowDot(1.0 * LEFT, radius=0.5, color=WHITE)
|
|
|
|
self.play(FadeIn(eye_dot))
|
|
self.wait(3)
|
|
self.play(
|
|
eye_dot.animate.move_to(1.5 * LEFT + 2 * DOWN).set_anim_args(path_arc=45 * DEGREES),
|
|
run_time=12,
|
|
rate_func=there_and_back,
|
|
)
|
|
self.play(FadeOut(eye_dot))
|
|
|
|
# Show recreation
|
|
right_rect = FullScreenRectangle().set_fill(BLACK, 1)
|
|
right_rect.stretch(0.35, 0, about_edge=RIGHT)
|
|
film_rect = Rectangle()
|
|
film_rect.set_fill(interpolate_color(RED_E, BLACK, 0.8), 1).set_stroke(WHITE, 1)
|
|
film_rect.rotate(80 * DEGREES, UP)
|
|
film_rect.rotate(20 * DEGREES, RIGHT)
|
|
film_rect.move_to(right_rect.get_left())
|
|
|
|
laser_point = RIGHT_SIDE + RIGHT
|
|
laser_light = VGroup(
|
|
Line(laser_point, film_rect.pfp(a + random.random() / 250))
|
|
for a in np.linspace(0, 1, 250)
|
|
)
|
|
laser_light.set_stroke(GREEN_SCREEN, (0, 1), 0.2)
|
|
laser_light.shuffle()
|
|
|
|
self.play(
|
|
FadeOut(lamp),
|
|
FadeOut(light),
|
|
FadeOut(scene),
|
|
FadeOut(scene_top),
|
|
FadeIn(right_rect),
|
|
FadeIn(film_rect),
|
|
)
|
|
self.play(ShowCreation(laser_light, lag_ratio=0.001))
|
|
self.wait(12)
|
|
self.wait(2) # Just in case
|
|
|
|
|
|
class DiffractionGratingScene(InteractiveScene):
|
|
light_position = 10 * DOWN + 5 * OUT + 3 * LEFT
|
|
|
|
def setup(self):
|
|
super().setup()
|
|
self.camera.light_source.move_to(self.light_position)
|
|
|
|
def get_wall_with_slits(self, n_slits, spacing=1.0, slit_width=0.1, height=0.25, depth=3.0, total_width=40, color=GREY_D, shading=(0.5, 0.5, 0.5)):
|
|
width = spacing - slit_width
|
|
cube = Cube().set_shape(width, height, depth)
|
|
parts = cube.replicate(n_slits + 1)
|
|
parts.arrange(RIGHT, buff=slit_width)
|
|
edge_piece_width = 0.5 * (total_width - parts.get_width()) + parts[0].get_width()
|
|
parts[0].set_width(edge_piece_width, stretch=True, about_edge=RIGHT)
|
|
parts[-1].set_width(edge_piece_width, stretch=True, about_edge=LEFT)
|
|
|
|
parts.set_color(color)
|
|
parts.set_shading(*shading)
|
|
return parts
|
|
|
|
def get_point_sources_from_wall(self, wall, z=0):
|
|
sources = GlowDots(np.array([
|
|
midpoint(p1.get_right(), p2.get_left())
|
|
for p1, p2 in zip(wall, wall[1:])
|
|
]))
|
|
sources.set_color(WHITE)
|
|
sources.set_z(z)
|
|
return sources
|
|
|
|
def get_plane_wave(self, direction=UP):
|
|
return LightWaveSlice(DotCloud([-1000 * direction]), decay_factor=0)
|
|
|
|
def get_graph_over_wave(self, line, light_wave, color=WHITE, stroke_width=2, direction=OUT, scale_factor=0.5, n_curves=500):
|
|
line.insert_n_curves(n_curves - line.get_num_curves())
|
|
graph = line.copy()
|
|
graph.line = line
|
|
|
|
def update_graph(graph):
|
|
points = graph.line.get_anchors()
|
|
values = scale_factor * light_wave.wave_func(points)
|
|
graph.set_points_smoothly(points + values[:, np.newaxis] * direction)
|
|
|
|
graph.add_updater(update_graph)
|
|
graph.apply_depth_test()
|
|
graph.set_stroke(color, stroke_width)
|
|
return graph
|
|
|
|
|
|
class LightExposingFilm(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Set up wave
|
|
frame = self.frame
|
|
self.set_floor_plane("xz")
|
|
|
|
source_dist = 16.5
|
|
source = GlowDot(source_dist * OUT).set_opacity(0)
|
|
wave = LightWaveSlice(source, decay_factor=0, wave_number=0.5)
|
|
wave.set_opacity(0)
|
|
wave_line = Line(source.get_center(), ORIGIN)
|
|
wave_line.set_stroke(width=0)
|
|
initial_wave_amp = 0.75
|
|
wave_amp_tracker = ValueTracker(initial_wave_amp)
|
|
graph = self.get_graph_over_wave(wave_line, wave, direction=UP, scale_factor=wave_amp_tracker.get_value(), n_curves=200)
|
|
graph.add_updater(lambda m: m.stretch(wave_amp_tracker.get_value() / initial_wave_amp, dim=1))
|
|
|
|
# Set up linear vector field
|
|
def field_func(points):
|
|
result = np.zeros_like(points)
|
|
result[:, 1] = wave_amp_tracker.get_value() * wave.wave_func(points)
|
|
return result
|
|
|
|
linear_field = VectorField(field_func, sample_points=wave_line.get_points()[::4], max_vect_len=2.0)
|
|
linear_field.always.update_vectors()
|
|
linear_field.set_stroke(WHITE, width=1.5, opacity=0.75)
|
|
|
|
# Add film
|
|
film = Rectangle(16, 9)
|
|
film.set_fill(GREY_E, 0.75)
|
|
film.set_height(8)
|
|
film.center()
|
|
|
|
exp_source = GlowDot(OUT).set_opacity(0)
|
|
exposure = LightIntensity(exp_source)
|
|
exposure.set_color(GREEN)
|
|
exposure.set_decay_factor(3)
|
|
exposure.set_max_amp(0.15)
|
|
exposure.set_opacity(0.7)
|
|
exposure.replace(film, stretch=True)
|
|
|
|
film_label = Text("Film", font_size=96)
|
|
film_label.next_to(film, UP, MED_SMALL_BUFF)
|
|
|
|
frame.reorient(-18, -7, 0, (0.46, -0.4, -2.46), 17.86)
|
|
self.add(film, exposure, film_label)
|
|
self.add(wave, linear_field, graph)
|
|
|
|
# Fade in
|
|
self.play(
|
|
frame.animate.reorient(-88, -4, 0, (3.56, -0.71, 4.59), 14.60),
|
|
FadeIn(exposure, time_span=(0, 3)),
|
|
run_time=7.5
|
|
)
|
|
|
|
# Label amplitude
|
|
low_line = DashedLine(8 * OUT, ORIGIN)
|
|
high_line = low_line.copy().shift(wave_amp_tracker.get_value() * UP)
|
|
amp_lines = VGroup(low_line, high_line)
|
|
amp_lines.set_stroke(YELLOW, 3)
|
|
brace = Brace(amp_lines, RIGHT)
|
|
brace.rotate(PI / 2, DOWN)
|
|
brace.next_to(amp_lines, OUT, SMALL_BUFF)
|
|
amp_label = Tex(R"\text{Amplitude}", font_size=72)
|
|
amp_label.set_backstroke(BLACK, 5)
|
|
amp_label.rotate(PI / 2, DOWN)
|
|
amp_label.next_to(brace, OUT, SMALL_BUFF)
|
|
fade_rect = Rectangle(8, 3)
|
|
fade_rect.rotate(PI / 2, DOWN)
|
|
fade_rect.next_to(amp_lines, OUT, buff=0)
|
|
fade_rect.set_stroke(BLACK, 0)
|
|
fade_rect.set_fill(BLACK, 0.7)
|
|
|
|
self.play(
|
|
FadeIn(fade_rect),
|
|
GrowFromCenter(brace),
|
|
FadeIn(amp_lines[0]),
|
|
ReplacementTransform(amp_lines[0].copy().fade(1), amp_lines[1]),
|
|
)
|
|
self.play(Write(amp_label, stroke_color=WHITE, lag_ratio=0.1, run_time=2.0))
|
|
self.wait_until(lambda: 8 / 30 < wave.uniforms["time"] % 1 < 9 / 30)
|
|
|
|
# Label phase
|
|
phase_text = Text("Phase", font_size=72)
|
|
phase_circle = Circle(radius=0.75)
|
|
phase_circle.set_stroke(BLUE, 2)
|
|
phase_circle.next_to(phase_text, DOWN)
|
|
phase_vect = Arrow(phase_circle.get_center(), phase_circle.get_right(), buff=0, thickness=2)
|
|
phase_vect.set_fill(BLUE)
|
|
|
|
phase_label = VGroup(phase_text, phase_circle, phase_vect)
|
|
phase_label.rotate(PI / 2, DOWN)
|
|
phase_label.next_to(amp_lines, DOWN, buff=2)
|
|
phase_label.set_z(2)
|
|
og_phase_label = phase_label.copy()
|
|
|
|
wavelength = 1.0 / wave.uniforms["wave_number"]
|
|
start_z = 0
|
|
phase_line = Line(start_z * OUT, (start_z + wavelength) * OUT)
|
|
phase_line.set_stroke(BLUE, 3)
|
|
|
|
phase_arrow = Arrow(phase_text.get_corner(IN + UP) + 0.2 * (OUT + UP), phase_line.get_start(), buff=0)
|
|
phase_arrow.always.set_perpendicular_to_camera(self.frame)
|
|
|
|
self.play(
|
|
wave.animate.pause(),
|
|
FadeIn(phase_label),
|
|
FadeIn(phase_arrow),
|
|
frame.animate.reorient(-99, -2, 0, (3.56, -0.71, 4.59), 14.60),
|
|
)
|
|
self.play(
|
|
Rotate(phase_vect, -TAU, axis=LEFT, about_point=phase_vect.get_start()),
|
|
ShowCreation(phase_line),
|
|
phase_arrow.animate.put_start_and_end_on(phase_arrow.get_start(), phase_line.get_end()),
|
|
run_time=3
|
|
)
|
|
self.play(
|
|
Rotate(phase_vect, TAU, axis=LEFT, about_point=phase_vect.get_start()),
|
|
phase_arrow.animate.put_start_and_end_on(phase_arrow.get_start(), phase_line.get_start()),
|
|
run_time=3
|
|
)
|
|
self.wait(2)
|
|
|
|
# Decrease and increase amplitude
|
|
amp_group = VGroup(amp_lines, brace)
|
|
amp_group.f_always.set_height(wave_amp_tracker.get_value, stretch=lambda: True)
|
|
amp_group.always.move_to(ORIGIN, IN + DOWN)
|
|
amp_label.always.next_to(brace, OUT, SMALL_BUFF)
|
|
self.add(amp_group, amp_label)
|
|
|
|
phase_vect.add_updater(lambda m: m.put_start_and_end_on(
|
|
phase_circle.get_center(),
|
|
phase_circle.pfp((wave.uniforms["frequency"] * wave.uniforms["time"]) % 1),
|
|
))
|
|
phase_vect.always.set_perpendicular_to_camera(self.frame)
|
|
|
|
self.play(
|
|
wave.animate.set_time_rate(0.5),
|
|
frame.animate.reorient(-55, -13, 0, (2.59, -0.61, 2.18), 17.00),
|
|
phase_arrow.animate.fade(0.8),
|
|
phase_label.animate.fade(0.8),
|
|
FadeOut(phase_line),
|
|
run_time=3
|
|
)
|
|
self.play(
|
|
wave_amp_tracker.animate.set_value(0.25),
|
|
exposure.animate.set_opacity(0.25),
|
|
run_time=3,
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
wave_amp_tracker.animate.set_value(1.0),
|
|
exposure.animate.set_opacity(1.0),
|
|
run_time=4
|
|
)
|
|
self.wait()
|
|
|
|
# Write exposure expression
|
|
exp_expr = Tex(R"\text{Exposure} = c \cdot |\text{Amplitude}|^2", font_size=72)
|
|
exp_expr.move_to(2 * UP)
|
|
|
|
self.play(
|
|
LaggedStart(
|
|
Write(exp_expr[R"\text{Exposure} = c \cdot |"][0]),
|
|
TransformFromCopy(amp_label.copy().clear_updaters(), exp_expr[R"\text{Amplitude}"][0]),
|
|
Write(exp_expr[R"|^2"][0]),
|
|
lag_ratio=0.1,
|
|
),
|
|
frame.animate.reorient(-34, -15, 0, (1.48, -0.23, 1.29), 16.08),
|
|
run_time=3,
|
|
)
|
|
self.wait(8)
|
|
|
|
# Focus on phase again
|
|
to_fade = VGroup(fade_rect, amp_group, amp_label)
|
|
wave.pause()
|
|
self.play(
|
|
FadeOut(to_fade, lag_ratio=0.01, time_span=(0, 1.5)),
|
|
phase_label.animate.match_style(og_phase_label),
|
|
phase_arrow.animate.set_fill(opacity=1),
|
|
frame.animate.reorient(-86, -4, 0, (2.43, -0.97, 1.94), 12.91),
|
|
run_time=2
|
|
)
|
|
|
|
# Shift back the phase
|
|
shift_label = TexText(R"Shift back phase $\rightarrow$")
|
|
shift_label.rotate(PI / 2, DOWN)
|
|
shift_label.move_to(7 * OUT + 1.5 * DOWN)
|
|
|
|
self.play(
|
|
wave.animate.set_uniform(time_rate=-0.5).set_anim_args(rate_func=there_and_back),
|
|
FadeIn(shift_label, shift=OUT),
|
|
run_time=2.0
|
|
)
|
|
self.play(FadeOut(shift_label))
|
|
self.wait()
|
|
|
|
# Play for a while
|
|
self.play(wave.animate.set_time_rate(0.5))
|
|
self.wait(16)
|
|
|
|
# Shine a second beam in
|
|
source2 = GlowDot(source_dist * (OUT + RIGHT))
|
|
wave2 = LightWaveSlice(source2)
|
|
wave2.set_uniforms(dict(wave.uniforms))
|
|
wave2.set_opacity(0)
|
|
line2 = Line(ORIGIN, source2.get_center())
|
|
ref_amp = 0.75
|
|
graph2 = self.get_graph_over_wave(line2, wave2, direction=UP, scale_factor=ref_amp, n_curves=int(200 * math.sqrt(2)))
|
|
graph2.set_color(YELLOW)
|
|
graph2.update()
|
|
|
|
def field_func2(points):
|
|
result = np.zeros_like(points)
|
|
result[:, 1] = ref_amp * wave2.wave_func(points)
|
|
return result
|
|
|
|
linear_field2 = VectorField(field_func2, sample_points=line2.get_points()[::4], max_vect_len=2.0)
|
|
linear_field2.always.update_vectors()
|
|
linear_field2.set_stroke(YELLOW, width=1.5, opacity=0.75)
|
|
|
|
ref_wave_label = Text("Reference Wave", font_size=72)
|
|
ref_wave_label.set_color(YELLOW)
|
|
ref_wave_label.set_backstroke(BLACK, 3)
|
|
ref_wave_label.rotate(PI / 4, DOWN)
|
|
ref_wave_label.move_to([5, 1.25, 5])
|
|
|
|
self.play(
|
|
FadeOut(phase_label),
|
|
FadeOut(phase_arrow),
|
|
wave_amp_tracker.animate.set_value(0.75),
|
|
exposure.animate.set_opacity(0.75),
|
|
frame.animate.reorient(-32, -20, 0, (1.48, -0.73, 0.84), 14.72),
|
|
run_time=2,
|
|
)
|
|
self.wait_until(lambda: 8 / 30 < wave.uniforms["time"] % 1 < 9 / 30)
|
|
self.add(wave2)
|
|
self.play(
|
|
FadeIn(graph2),
|
|
FadeIn(linear_field2),
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(ref_wave_label, shift=UP))
|
|
self.wait(5)
|
|
|
|
# Zoom in
|
|
self.play(
|
|
frame.animate.reorient(-58, -18, 0, (3.29, 0.39, 0.55), 10.68),
|
|
run_time=4
|
|
)
|
|
self.play(
|
|
exposure.animate.set_opacity(1).set_max_amp(0.1),
|
|
run_time=2
|
|
)
|
|
self.wait(3)
|
|
|
|
# Shift back
|
|
shift_label = TexText(R"Shift back phase $\rightarrow$")
|
|
shift_label.rotate(PI / 2, DOWN)
|
|
shift_label.set_backstroke(BLACK, 5)
|
|
shift_label.move_to(5 * OUT + 1.25 * UP)
|
|
|
|
self.play(
|
|
wave.animate.pause(),
|
|
wave2.animate.pause(),
|
|
)
|
|
self.play(
|
|
wave.animate.set_uniform(time_rate=-0.5).set_anim_args(rate_func=there_and_back),
|
|
exposure.animate.set_max_amp(0.2).set_opacity(0.15),
|
|
FadeIn(shift_label, OUT),
|
|
run_time=2.0
|
|
)
|
|
self.play(FadeOut(shift_label))
|
|
self.play(
|
|
wave.animate.set_time_rate(0.5),
|
|
wave2.animate.set_time_rate(0.5),
|
|
)
|
|
self.play(
|
|
frame.animate.reorient(-54, -16, 0, (3.01, -0.09, 0.55), 14.47),
|
|
run_time=10,
|
|
)
|
|
|
|
wave.pause()
|
|
wave2.pause()
|
|
self.play(
|
|
wave.animate.set_uniform(time_rate=-0.5).set_anim_args(rate_func=there_and_back),
|
|
exposure.animate.set_max_amp(0.1).set_opacity(1),
|
|
FadeIn(shift_label, OUT),
|
|
run_time=2.0
|
|
)
|
|
self.play(FadeOut(shift_label))
|
|
wave.set_time_rate(0.5)
|
|
wave2.set_time_rate(0.5)
|
|
self.play(
|
|
frame.animate.reorient(15, -22, 0, (2.14, -0.48, -0.07), 15.51),
|
|
run_time=12,
|
|
)
|
|
|
|
wave.pause()
|
|
wave2.pause()
|
|
self.play(
|
|
wave.animate.set_uniform(time_rate=-0.5).set_anim_args(rate_func=there_and_back),
|
|
exposure.animate.set_max_amp(0.2).set_opacity(0.15),
|
|
run_time=2.0
|
|
)
|
|
wave.set_time_rate(0.5)
|
|
wave2.set_time_rate(0.5)
|
|
self.play(
|
|
frame.animate.reorient(15, -14, 0, (1.47, 0.09, -0.04), 13.26),
|
|
run_time=12,
|
|
)
|
|
|
|
|
|
class TwoInterferingWaves(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Setup reference and object waves
|
|
frame = self.frame
|
|
self.set_floor_plane("xz")
|
|
|
|
film_border = ScreenRectangle()
|
|
film_border.set_height(6)
|
|
film_border.set_fill(BLACK, 0)
|
|
film_border.set_stroke(WHITE, 1)
|
|
film_image = ImageMobject("HologramFilm.jpg")
|
|
film_image.replace(film_border)
|
|
|
|
frame.set_height(6)
|
|
self.add(film_image, film_border)
|
|
|
|
# Add waves
|
|
obj_vect = 5 * OUT + 2 * LEFT
|
|
ref_vect = 5 * OUT + 2 * RIGHT
|
|
lp = film_border.get_left() + 3 * DOWN
|
|
rp = film_border.get_right() + 3 * DOWN
|
|
|
|
obj_points = DotCloud(np.random.random((31, 3)))
|
|
obj_points.set_height(10)
|
|
obj_points.move_to(obj_vect)
|
|
obj_wave = LightWaveSlice(obj_points)
|
|
obj_wave.set_wave_number(8)
|
|
obj_wave.set_frequency(1)
|
|
obj_wave.set_max_amp(3)
|
|
|
|
ref_wave = LightWaveSlice(TrueDot(10 * ref_vect))
|
|
ref_wave.match_points(obj_wave)
|
|
ref_wave.set_uniforms(dict(obj_wave.uniforms))
|
|
ref_wave.set_max_amp(1.25)
|
|
ref_wave.set_decay_factor(0)
|
|
ref_wave.set_opacity(0.85)
|
|
|
|
rect = film_border
|
|
obj_wave.set_points([
|
|
2 * obj_vect, rect.get_corner(UL), rect.get_corner(DL),
|
|
2 * obj_vect, rect.get_corner(UL), rect.get_corner(UR),
|
|
2 * obj_vect, rect.get_corner(UR), rect.get_corner(DR),
|
|
2 * obj_vect, rect.get_corner(DR), rect.get_corner(DL),
|
|
])
|
|
ref_wave.set_points([
|
|
2 * ref_vect, rect.get_corner(UL), rect.get_corner(DL),
|
|
2 * ref_vect, rect.get_corner(UL), rect.get_corner(UR),
|
|
2 * ref_vect, rect.get_corner(UR), rect.get_corner(DR),
|
|
2 * ref_vect, rect.get_corner(DR), rect.get_corner(DL),
|
|
])
|
|
obj_wave.deactivate_depth_test()
|
|
ref_wave.deactivate_depth_test()
|
|
|
|
waves = Group(ref_wave, obj_wave)
|
|
waves.set_opacity(0.25)
|
|
|
|
self.play(
|
|
FadeIn(waves),
|
|
frame.animate.reorient(0, -21, 0, (-0.09, -0.86, 0.04), 9.40),
|
|
run_time=3
|
|
)
|
|
self.wait(3)
|
|
|
|
# Diversion into backdrop for complex wave scene
|
|
if False:
|
|
# Isolate object wave
|
|
new_obj_wave = LightWaveSlice(obj_points)
|
|
new_obj_wave.set_points([2 * obj_vect, rect.get_left(), rect.get_right()])
|
|
new_obj_wave.set_uniforms(dict(obj_wave.uniforms))
|
|
new_obj_wave.set_opacity(1)
|
|
new_obj_wave.set_frequency(0.25)
|
|
|
|
frame.reorient(0, -19, 0, (0.01, -0.03, -0.28), 12.53)
|
|
waves.set_opacity(0.4)
|
|
for wave in waves:
|
|
wave.set_frequency(0.25)
|
|
self.wait(4)
|
|
|
|
self.play(
|
|
FadeOut(ref_wave),
|
|
FadeOut(film_border),
|
|
FadeOut(film_image),
|
|
FadeOut(obj_wave),
|
|
FadeIn(new_obj_wave)
|
|
)
|
|
|
|
# Go to 2d slice
|
|
self.play(
|
|
new_obj_wave.animate.scale(10),
|
|
frame.animate.reorient(-3, -5, 0, (-0.96, 0.03, -0.15), 5.32),
|
|
run_time=5
|
|
)
|
|
self.wait(4)
|
|
|
|
# Circle a point
|
|
circle = Circle(radius=0.1).set_stroke(WHITE, 2)
|
|
circle.move_to(LEFT + 2 * DOWN)
|
|
circle.reverse_points()
|
|
full_rect = FullScreenRectangle()
|
|
full_rect.set_fill(BLACK, 0.75)
|
|
full_rect.append_points([full_rect.get_end(), *circle.get_points()])
|
|
|
|
circle.fix_in_frame()
|
|
full_rect.fix_in_frame()
|
|
|
|
self.play(
|
|
FadeIn(full_rect),
|
|
ShowCreation(circle),
|
|
)
|
|
self.wait(8)
|
|
|
|
# Add exposure
|
|
exposure = LightIntensity(obj_points)
|
|
exposure.set_color(WHITE)
|
|
exposure.replace(film_border, stretch=True)
|
|
exposure.set_wave_number(256)
|
|
exposure.set_max_amp(4)
|
|
|
|
# One more insertion
|
|
exposure.set_opacity(0.5)
|
|
self.add(exposure, waves)
|
|
frame.reorient(-18, -30, 0, (-5.85, -0.18, -0.89), 15.16)
|
|
self.play(
|
|
frame.animate.reorient(17, -24, 0, (-5.5, -0.82, -0.67), 15.86),
|
|
run_time=30
|
|
)
|
|
|
|
self.add(exposure, waves)
|
|
self.play(
|
|
FadeIn(exposure, run_time=5),
|
|
FadeOut(waves, time_span=(3, 5)),
|
|
)
|
|
|
|
# Zoom in on the film
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (0.5, 0.07, 0.0), 0.15),
|
|
film_image.animate.set_opacity(0.1).set_anim_args(time_span=(0, 5)),
|
|
run_time=8,
|
|
)
|
|
self.play(frame.animate.reorient(0, 0, 0, (0.41, 0.03, 0.0), 0.05), run_time=8)
|
|
|
|
|
|
class SinglePointOnFilm(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Reference image
|
|
img = ImageMobject("SinglePointHologram")
|
|
img.set_height(FRAME_HEIGHT)
|
|
img.fix_in_frame()
|
|
img.set_opacity(0.75)
|
|
# self.add(img)
|
|
|
|
# Create object and reference wave
|
|
# Much of this copied from CreateZonePlate below
|
|
frame = self.frame
|
|
frame.set_field_of_view(42 * DEGREES)
|
|
axes = ThreeDAxes()
|
|
self.set_floor_plane("xz")
|
|
|
|
wave_width = 100
|
|
wave_number = 2
|
|
frequency = 1
|
|
|
|
source_point = GlowDot(color=WHITE, radius=0.5)
|
|
source_point.move_to([0., -0.75, 4.62])
|
|
obj_wave = LightWaveSlice(source_point)
|
|
obj_wave.set_decay_factor(0.7)
|
|
|
|
obj_wave.set_width(wave_width)
|
|
obj_wave.rotate(PI / 2, RIGHT, about_point=ORIGIN)
|
|
obj_wave.move_to(source_point)
|
|
obj_wave.set_wave_number(wave_number)
|
|
obj_wave.set_frequency(frequency)
|
|
|
|
# Add film
|
|
plate_border = Rectangle(4, 4)
|
|
plate_border.set_fill(BLACK, 0)
|
|
plate_border.set_stroke(WHITE, 2)
|
|
plate_body = Square3D()
|
|
plate_body.set_color(BLACK, 0.9)
|
|
plate_body.replace(plate_border, stretch=True)
|
|
plate_body.set_shading(0.1, 0.1, 0)
|
|
|
|
plate = Group(plate_body, plate_border)
|
|
plate.center()
|
|
|
|
film_label = Text("Film")
|
|
film_label.next_to(plate, UP)
|
|
film_label.set_backstroke(BLACK, 3)
|
|
film_label.set_z_index(1)
|
|
|
|
# Spherical waves
|
|
wave_stack = Group(
|
|
obj_wave.copy().rotate(PI / 2, OUT).set_opacity(0.1)
|
|
for x in range(30)
|
|
)
|
|
wave_stack.set_opacity(0.07)
|
|
wave_stack.arrange_to_fit_width(4)
|
|
wave_stack.sort(lambda p: -p[0])
|
|
wave_stack.move_to(obj_wave)
|
|
|
|
# Label object wave
|
|
source_label = Text("Object (idealized point)", font_size=36)
|
|
source_label.next_to(source_point, UR, buff=MED_LARGE_BUFF)
|
|
source_label.shift(0.25 * UL)
|
|
source_label.set_backstroke(BLACK, 2)
|
|
source_arrow = Arrow(source_label["Object"].get_bottom(), source_point.get_center(), buff=0.1)
|
|
source_arrow.set_perpendicular_to_camera(self.frame)
|
|
source_label.add(source_arrow)
|
|
source_label.rotate(PI / 2, DOWN, about_point=source_point.get_center())
|
|
obj_point = TrueDot()
|
|
obj_point.move_to(source_point)
|
|
|
|
obj_wave_label = Text("Object wave")
|
|
obj_wave_label.rotate(PI / 2, LEFT)
|
|
obj_wave_label.next_to(source_point, IN, buff=0.1)
|
|
obj_wave_label.set_backstroke(BLACK, 2)
|
|
|
|
frame.reorient(-88, -12, 0, (0.22, -0.81, 4.62), 13.76)
|
|
self.add(plate, film_label)
|
|
self.add(obj_wave, wave_stack, obj_point, source_point)
|
|
self.add(source_label)
|
|
self.wait()
|
|
|
|
# Slowly reorient
|
|
self.play(
|
|
frame.animate.reorient(-14, -11, 0, (0.27, 0.14, 3.59), 6.17),
|
|
Rotate(source_label, PI / 2, UP, about_point=source_point.get_center()),
|
|
Rotate(wave_stack, PI / 2, UP, about_point=source_point.get_center()),
|
|
run_time=10
|
|
)
|
|
|
|
# Look from above
|
|
self.play(
|
|
FadeOut(wave_stack),
|
|
FadeOut(source_label),
|
|
FadeOut(plate),
|
|
FadeOut(film_label),
|
|
obj_wave.animate.set_decay_factor(0.5),
|
|
frame.animate.reorient(0, -90, 0, source_point.get_center(), 4).set_anim_args(run_time=4)
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
class ExplainWaveVisualization(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Set up the wave
|
|
full_width = 20
|
|
frame = self.frame
|
|
|
|
source = GlowDot(ORIGIN, color=WHITE)
|
|
source.set_radius(0.5)
|
|
wave = LightWaveSlice(source)
|
|
wave.set_width(full_width)
|
|
wave.move_to(ORIGIN)
|
|
|
|
frame.reorient
|
|
self.add(wave, source)
|
|
|
|
# Talk through what's displayed
|
|
def field_func(points):
|
|
result = np.zeros_like(points)
|
|
result[:, 2] = 0.5 * wave.wave_func(points)
|
|
return result
|
|
|
|
linear_field = VectorField(
|
|
field_func,
|
|
sample_points=np.linspace(ORIGIN, 10 * UP, 100),
|
|
max_vect_len=1.0,
|
|
)
|
|
linear_field.always.update_vectors()
|
|
linear_field.set_stroke(WHITE, width=1.5, opacity=0.75)
|
|
full_field = VectorField(
|
|
field_func,
|
|
width=full_width,
|
|
height=full_width,
|
|
x_density=5,
|
|
y_density=5,
|
|
max_vect_len=0.5,
|
|
)
|
|
full_field.sample_points
|
|
full_field.set_stroke(WHITE, width=1.5, opacity=0.25)
|
|
full_field.always.update_vectors()
|
|
|
|
self.play(
|
|
frame.animate.reorient(71, 77, 0, (-0.62, 0.7, 0.19), 3.11),
|
|
wave.animate.set_uniform(time_rate=0.5).set_anim_args(suspend_mobject_updating=False),
|
|
VFadeIn(full_field, time_span=(0, 2)),
|
|
run_time=6
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
VFadeIn(linear_field),
|
|
VFadeOut(full_field),
|
|
)
|
|
self.wait(5)
|
|
self.wait_until(lambda: 15 / 30 < wave.uniforms["time"] % 1 < 16 / 30)
|
|
self.play(
|
|
wave.animate.pause().set_anim_args(suspend_mobject_updating=False),
|
|
)
|
|
self.wait()
|
|
|
|
# Show example vectors
|
|
sample_vect = Vector(OUT, thickness=1.0)
|
|
sample_vect.set_fill(WHITE, 1, border_width=0.5)
|
|
sample_vect.base_point = Dot(0.705 * UP, fill_color=BLUE, radius=0.02)
|
|
|
|
def update_sample_vect(vect):
|
|
point = vect.base_point.get_center()
|
|
vect.put_start_and_end_on(point, point + 0.95 * field_func([point])[0])
|
|
vect.set_perpendicular_to_camera(self.frame)
|
|
|
|
update_sample_vect(sample_vect)
|
|
|
|
self.play(
|
|
linear_field.animate.set_stroke(opacity=0.2),
|
|
GrowArrow(sample_vect, run_time=2),
|
|
frame.animate.reorient(81, 84, 0, (-0.5, 0.69, 0.23), 1.65).set_anim_args(run_time=3)
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(sample_vect.base_point, 0.1 * IN))
|
|
self.wait()
|
|
sample_vect.add_updater(update_sample_vect)
|
|
self.play(sample_vect.base_point.animate.move_to(1.315 * UP).set_color(RED), run_time=3)
|
|
self.wait()
|
|
self.play(sample_vect.base_point.animate.move_to(1.01 * UP).set_color(GREY_E), run_time=2)
|
|
self.wait()
|
|
self.play(
|
|
VFadeOut(sample_vect, time_span=(0, 2)),
|
|
FadeOut(sample_vect.base_point, time_span=(0, 2)),
|
|
linear_field.animate.set_stroke(opacity=0.75).set_anim_args(time_span=(0, 2)),
|
|
wave.animate.set_uniform(time_rate=0.5).set_anim_args(suspend_mobject_updating=False),
|
|
frame.animate.reorient(49, 79, 0, (-0.14, 1.34, 0.23), 2.78),
|
|
run_time=5,
|
|
)
|
|
self.wait(2)
|
|
|
|
# Show full 3d field
|
|
full_field = VectorField(
|
|
lambda p: 0.5 * field_func(p),
|
|
x_density=4,
|
|
y_density=4,
|
|
z_density=2,
|
|
depth=4,
|
|
max_vect_len=0.5,
|
|
)
|
|
full_field.set_stroke(WHITE, width=1, opacity=0.25)
|
|
full_field.always.update_vectors()
|
|
|
|
added_waves = Group(
|
|
wave.copy().rotate(PI / 2, UP).shift(z * RIGHT).set_opacity(0.1)
|
|
for z in np.arange(-2, 2, 0.1)
|
|
)
|
|
|
|
self.play(
|
|
# VFadeIn(full_field),
|
|
VFadeOut(linear_field),
|
|
FadeIn(added_waves, suspend_mobject_updating=False),
|
|
frame.animate.reorient(48, 79, 0, (-0.42, 1.07, 0.35), 4.18).set_anim_args(run_time=8)
|
|
)
|
|
wave.save_state()
|
|
wave.scale(0)
|
|
self.play(
|
|
Restore(wave, suspend_mobject_updating=False),
|
|
FadeOut(added_waves, time_span=(0, 1), suspend_mobject_updating=False),
|
|
run_time=3,
|
|
)
|
|
self.wait(2)
|
|
|
|
# Show a sine wave
|
|
line = Line(ORIGIN, 10 * UR)
|
|
line.shift(source.get_center())
|
|
line.set_stroke(TEAL, 2)
|
|
graph = self.get_graph_over_wave(line, wave)
|
|
graph.set_stroke(WHITE, 2)
|
|
wave.set_z_index(1)
|
|
source.set_z_index(1)
|
|
|
|
linear_field = VectorField(
|
|
field_func,
|
|
sample_points=line.get_anchors()[::2],
|
|
max_vect_len=1.0,
|
|
)
|
|
linear_field.set_stroke(WHITE, 0.75)
|
|
linear_field.always.update_vectors()
|
|
|
|
self.add(line, graph)
|
|
self.play(
|
|
ShowCreation(line, time_span=(0, 3)),
|
|
ShowCreation(graph, time_span=(0, 3)),
|
|
VFadeIn(linear_field, time_span=(0, 3), suspend_mobject_updating=False),
|
|
frame.animate.reorient(0, 73, 0, (-0.03, 1.7, 0.2), 4.65),
|
|
run_time=10
|
|
)
|
|
self.wait(8)
|
|
|
|
|
|
class CreateZonePlate(DiffractionGratingScene):
|
|
samples = 4
|
|
|
|
def construct(self):
|
|
# Create object and reference wave
|
|
frame = self.frame
|
|
axes = ThreeDAxes()
|
|
self.set_floor_plane("xz")
|
|
|
|
wave_width = 100
|
|
wave_number = 4
|
|
frequency = 1
|
|
|
|
ref_wave = self.get_plane_wave(direction=IN)
|
|
ref_wave.set_opacity(0.75)
|
|
ref_source = ref_wave.point_sources
|
|
source_point = GlowDot(OUT, color=WHITE, radius=0.5)
|
|
obj_wave = LightWaveSlice(source_point)
|
|
obj_wave.set_decay_factor(0.7)
|
|
|
|
for wave in [obj_wave, ref_wave]:
|
|
wave.set_width(wave_width)
|
|
wave.rotate(PI / 2, RIGHT, about_point=ORIGIN)
|
|
wave.center()
|
|
wave.set_wave_number(wave_number)
|
|
wave.set_frequency(frequency)
|
|
|
|
frame.reorient(-32, -21, 0, (-0.74, 0.32, -0.49), 7.08)
|
|
|
|
def get_all_sources():
|
|
return np.vstack([
|
|
obj_wave.point_sources.get_points(),
|
|
ref_wave.point_sources.get_points()
|
|
])
|
|
|
|
# Add film
|
|
plate = Rectangle(16, 9)
|
|
plate.set_height(4)
|
|
plate.set_stroke(WHITE, 1, 0.5).set_fill(BLACK, 0.0)
|
|
plate.set_shading(0.1, 0.1, 0)
|
|
plate.apply_depth_test()
|
|
plate_body = Square3D()
|
|
plate_group = Group(plate_body, plate)
|
|
|
|
plate_body.set_color(BLACK, 0.9)
|
|
plate_body.set_shape(plate.get_width(), plate.get_height())
|
|
plate_body.move_to(plate.get_center() + 1e-2 * IN)
|
|
|
|
exposure = LightIntensity(DotCloud(get_all_sources()))
|
|
exposure.set_decay_factor(0)
|
|
exposure.set_wave_number(wave_number)
|
|
exposure.replace(plate, stretch=True).shift(1e-2 * OUT)
|
|
exposure.set_color(WHITE, 0.85)
|
|
|
|
film = Group(plate, exposure)
|
|
film.set_height(4)
|
|
film.set_z(-2)
|
|
|
|
film_label = Text("Film")
|
|
film_label.next_to(plate, UP)
|
|
film_label.set_backstroke(BLACK, 3)
|
|
film_label.set_z_index(1)
|
|
|
|
# Label object wave
|
|
source_label = Text("Object (idealized point)", font_size=24)
|
|
source_label.next_to(source_point, UR, buff=0)
|
|
source_label.shift(0.25 * UL)
|
|
source_label.set_backstroke(BLACK, 2)
|
|
source_arrow = Arrow(source_label["Object"].get_bottom(), source_point.get_center(), buff=0.1)
|
|
source_arrow.always.set_perpendicular_to_camera(self.frame)
|
|
obj_point = TrueDot()
|
|
obj_point.move_to(source_point)
|
|
|
|
obj_wave_label = Text("Object wave")
|
|
obj_wave_label.rotate(PI / 2, LEFT)
|
|
obj_wave_label.next_to(source_point, IN, buff=0.1)
|
|
obj_wave_label.set_backstroke(BLACK, 2)
|
|
|
|
frame.reorient(41, -9, 0, (-0.72, 0.27, -0.49), 6.75)
|
|
self.add(plate_group, obj_wave, film_label)
|
|
plate_body.move_to(plate.get_center() + 1e-2 * IN)
|
|
self.play(
|
|
FadeIn(source_point),
|
|
FadeIn(source_label),
|
|
GrowArrow(source_arrow),
|
|
frame.animate.reorient(-4, -10, 0, (-0.74, 0.32, -0.49), 7.08).set_anim_args(run_time=5)
|
|
)
|
|
self.play(
|
|
TransformMatchingStrings(source_label, obj_wave_label),
|
|
FadeOut(source_arrow),
|
|
frame.animate.reorient(0, -47, 0, (-0.74, 0.32, -0.49), 7.08).set_anim_args(run_time=3),
|
|
)
|
|
self.wait(3)
|
|
|
|
# Label reference wave
|
|
ref_wave.match_width(film, stretch=True)
|
|
ref_wave.move_to(film, IN)
|
|
ref_wave_label = Text("Reference wave")
|
|
ref_wave_label.rotate(PI / 2, LEFT)
|
|
ref_wave_label.next_to(obj_wave_label, OUT, buff=1.5)
|
|
ref_wave_label.set_backstroke(BLACK, 2)
|
|
|
|
wave_fronts = Group(
|
|
plate.copy().set_color([BLUE, RED][z % 2], 0.15).shift(0.5 * z * OUT)
|
|
for z in range(4, 16)
|
|
)
|
|
for front in wave_fronts:
|
|
front.add_updater(lambda m, dt: m.shift(dt * (frequency / wave_number) * IN))
|
|
|
|
self.play(
|
|
FadeOut(obj_wave, 0.1 * DOWN),
|
|
FadeOut(obj_wave_label),
|
|
FadeOut(source_point),
|
|
FadeIn(ref_wave, 0.1 * DOWN),
|
|
FadeIn(ref_wave_label),
|
|
frame.animate.reorient(0, -40, 0, (-0.16, 0.01, -0.24), 7.08).set_anim_args(run_time=4),
|
|
)
|
|
self.play(
|
|
FadeIn(wave_fronts, time_span=(0, 2), lag_ratio=0.05),
|
|
frame.animate.reorient(0, -37, 0, (-0.16, 0.01, -0.24), 7.08),
|
|
run_time=8
|
|
)
|
|
self.play(FadeOut(wave_fronts, run_time=2, lag_ratio=0.1))
|
|
self.add(ref_source)
|
|
|
|
# Put reference at an angle
|
|
angle = 60 * DEGREES
|
|
direction = rotate_vector(OUT, angle, axis=UP)
|
|
dist = ref_wave.get_depth()
|
|
ref_wave.save_state()
|
|
ref_wave.point_sources.save_state()
|
|
ref_wave.target = ref_wave.generate_target()
|
|
p0 = film.get_left()
|
|
p1 = film.get_right()
|
|
p2 = p0 + dist * direction
|
|
p3 = p1 + dist * direction
|
|
ref_wave.target.set_points([p2, p0, p3, p1])
|
|
|
|
self.play(
|
|
MoveToTarget(ref_wave, run_time=2),
|
|
Rotate(ref_wave.point_sources, angle, axis=UP, about_point=ORIGIN),
|
|
Rotate(ref_wave_label, angle, axis=UP, about_point=film.get_center()),
|
|
run_time=10,
|
|
rate_func=lambda t: there_and_back_with_pause(t, 0.5)
|
|
)
|
|
self.wait(8)
|
|
|
|
# Also show the object wave from this perspective
|
|
self.remove(ref_wave, ref_wave_label)
|
|
self.add(obj_wave, obj_wave_label, source_point)
|
|
obj_wave_label.shift(0.25 * OUT)
|
|
self.wait(8)
|
|
|
|
# Show combined wave
|
|
comb_label = Text("Combined wave")
|
|
comb_label.rotate(PI / 2, LEFT)
|
|
comb_label.next_to(ref_wave_label, UP)
|
|
comb_label.set_backstroke(BLACK, 3)
|
|
comb_wave = obj_wave.copy()
|
|
comb_wave.point_sources = DotCloud([
|
|
*(source_point.get_center() for x in range(2)),
|
|
*np.linspace(20 * OUT + 4 * LEFT, 20 * OUT + 4 * RIGHT, 25)
|
|
])
|
|
comb_wave.set_decay_factor(0.8)
|
|
comb_wave.set_max_amp(1.5)
|
|
|
|
self.remove(obj_wave, obj_wave_label)
|
|
self.add(comb_wave, comb_label, source_point)
|
|
self.wait(8)
|
|
|
|
# Preview exposure
|
|
self.add(exposure, comb_wave, comb_label)
|
|
self.play(FadeIn(exposure, run_time=3))
|
|
self.wait(8)
|
|
|
|
# Change to side view
|
|
self.play(
|
|
FadeOut(comb_label),
|
|
comb_wave.animate.set_opacity(0.1),
|
|
FadeOut(film_label),
|
|
FadeOut(exposure),
|
|
frame.animate.reorient(80, -2, 0, (-0.64, 0.23, -0.93), 4.34),
|
|
run_time=5
|
|
)
|
|
|
|
# Add graphs to middle
|
|
ref_source.get_center()
|
|
ref_source.move_to(source_point.get_center() + ((1000 // wave_number) * wave_number) * OUT)
|
|
ref_wave.set_uniform(time=obj_wave.uniforms["time"])
|
|
|
|
obj_color, ref_color = colors = [TEAL, YELLOW]
|
|
obj_line, ref_line = lines = VGroup(
|
|
Line(source_point.get_center(), film.get_center()).set_stroke(color, 1, 0.5)
|
|
for color in colors
|
|
)
|
|
ref_line.scale(2, about_point=ref_line.get_end())
|
|
ref_line.shift(0.02 * RIGHT)
|
|
|
|
obj_graph, ref_graph = graphs = VGroup(
|
|
self.get_graph_over_wave(line, wave, scale_factor=sf, direction=UP, color=color)
|
|
for line, color, wave, sf in zip(lines, colors, [obj_wave, ref_wave], [0.15, 0.1])
|
|
)
|
|
graphs.set_stroke(width=2, opacity=1)
|
|
|
|
obj_label = Text("Object wave", font_size=24).rotate(PI / 2, UP)
|
|
ref_label = Text("Reference wave", font_size=24).rotate(PI / 2, UP)
|
|
obj_label.set_color(obj_color).next_to(obj_graph, UP, aligned_edge=OUT)
|
|
obj_label.shift(0.1 * IN)
|
|
ref_label.set_color(ref_color).next_to(ref_graph, DOWN)
|
|
ref_label.match_z(obj_label).shift(OUT)
|
|
|
|
obj_wave.set_opacity(0)
|
|
obj_wave.set_decay_factor(0.5)
|
|
ref_wave.set_opacity(0)
|
|
|
|
comb_wave.set_z_index(1)
|
|
|
|
self.add(obj_wave, ref_wave)
|
|
self.play(
|
|
ShowCreation(obj_line),
|
|
ShowCreation(obj_graph),
|
|
FadeIn(obj_label, lag_ratio=0.1),
|
|
run_time=2
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
ShowCreation(ref_line),
|
|
ShowCreation(ref_graph),
|
|
FadeIn(ref_label, lag_ratio=0.1),
|
|
)
|
|
self.wait(4)
|
|
|
|
# Show middle exposure
|
|
round_exposure = self.get_round_exposure(exposure, radius=0.25)
|
|
|
|
self.play(
|
|
GrowFromCenter(round_exposure),
|
|
frame.animate.reorient(53, -15, 0, (-0.83, 0.12, -0.62), 5.25).set_anim_args(run_time=3),
|
|
)
|
|
|
|
# Look off center
|
|
exposure.replace(plate, stretch=True)
|
|
exposure.set_shape(0.5 * plate.get_width(), 0.25)
|
|
exposure.move_to(plate.get_center(), LEFT).shift(1e-2 * OUT)
|
|
full_exposure = exposure.copy()
|
|
full_exposure.replace(plate, stretch=True)
|
|
full_exposure.shift(2e-2 * OUT)
|
|
exposure.save_state()
|
|
exposure.stretch(0, 0, about_edge=LEFT)
|
|
|
|
O_point = obj_label[0].get_center()
|
|
obj_label.add_updater(
|
|
lambda m: m.rotate(
|
|
angle_of_vector((m[-1].get_center() - m[0].get_center())[0::2]) - angle_of_vector(obj_line.get_vector()[0::2]),
|
|
axis=UP,
|
|
).shift(O_point - m[0].get_center())
|
|
)
|
|
|
|
trg_point = VectorizedPoint(plate.get_center())
|
|
obj_line.add_updater(lambda m: m.put_start_and_end_on(source_point.get_center(), trg_point.get_center()))
|
|
ref_line.add_updater(lambda m: m.move_to(trg_point.get_center() + 0.02 * RIGHT, IN))
|
|
|
|
self.play(
|
|
obj_wave.animate.pause(),
|
|
ref_wave.animate.pause(),
|
|
comb_wave.animate.pause(),
|
|
)
|
|
self.play(
|
|
trg_point.animate.move_to(film.get_right()),
|
|
round_exposure.animate.shift(0.01 * OUT),
|
|
MaintainPositionRelativeTo(ref_label, ref_line),
|
|
Restore(exposure),
|
|
frame.animate.reorient(35, -14, 0, (1.01, 0.23, -2.8), 8.40),
|
|
run_time=12
|
|
)
|
|
self.wait(2)
|
|
|
|
# Look to halfwavelength point
|
|
trg_x = 0.9
|
|
mid_line = Line(source_point.get_center(), plate.get_center())
|
|
mid_line.set_stroke(TEAL, 2)
|
|
mid_line_label = Tex("D", font_size=30).rotate(PI / 2, LEFT)
|
|
mid_line_label.next_to(mid_line, LEFT)
|
|
d_line_label = Tex(R"D + \frac{\lambda}{2}", font_size=30).rotate(PI / 2, LEFT)
|
|
VGroup(mid_line_label, d_line_label).set_fill(TEAL, 1)
|
|
|
|
self.play(
|
|
FadeOut(obj_label),
|
|
FadeOut(ref_label),
|
|
FadeOut(ref_line),
|
|
FadeOut(ref_graph),
|
|
frame.animate.reorient(0, -62, 0, (-0.27, -1.26, -1.17), 7.38),
|
|
trg_point.animate.move_to(plate.get_center() + trg_x * RIGHT),
|
|
run_time=3
|
|
)
|
|
d_line_label.next_to(obj_line.get_center(), RIGHT, buff=SMALL_BUFF)
|
|
self.play(
|
|
FadeIn(mid_line),
|
|
FadeIn(mid_line_label),
|
|
FadeIn(d_line_label),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
frame.animate.reorient(84, -9, 0, (-0.26, -0.27, -1.86), 3.23),
|
|
FadeOut(VGroup(mid_line, mid_line_label, d_line_label)),
|
|
FadeIn(ref_line),
|
|
FadeIn(ref_graph),
|
|
obj_wave.animate.unpause(),
|
|
ref_wave.animate.unpause(),
|
|
comb_wave.animate.unpause(),
|
|
run_time=5,
|
|
)
|
|
self.wait(5)
|
|
|
|
# Show the circle
|
|
circle = Circle(radius=trg_x)
|
|
circle.move_to(film)
|
|
circle.set_stroke(GREY_D, 1)
|
|
|
|
tail = TracingTail(circle.get_end, stroke_color=BLUE_D, stroke_width=(0, 3))
|
|
|
|
globals().update(locals())
|
|
self.add(tail)
|
|
self.wait()
|
|
self.add(circle, tail)
|
|
round_exposure.set_width(0.25)
|
|
self.play(
|
|
frame.animate.reorient(41, -15, 0, (-0.54, -0.17, -1.78), 4.65),
|
|
ShowCreation(circle),
|
|
UpdateFromFunc(trg_point, lambda m: m.move_to(circle.get_end())),
|
|
round_exposure.animate.set_width(3),
|
|
FadeOut(exposure, time_span=(0, 1)),
|
|
run_time=4
|
|
)
|
|
self.play(FadeOut(circle, run_time=2))
|
|
self.remove(tail)
|
|
self.wait(2)
|
|
|
|
# Grow rings fully
|
|
exposure.replace(plate, stretch=True).shift(0.01 * OUT)
|
|
|
|
self.add(exposure, round_exposure)
|
|
self.play(
|
|
FadeOut(round_exposure, run_time=2),
|
|
FadeIn(exposure, run_time=2),
|
|
comb_wave.animate.set_opacity(0.5).set_anim_args(time_span=(6, 8)),
|
|
frame.animate.reorient(0, -23, 0, (-0.14, 0.01, -2.23), 6.52).set_anim_args(run_time=8)
|
|
)
|
|
|
|
# Just kinda hang for a bit
|
|
time0 = self.time
|
|
globals().update(locals())
|
|
frame.add_updater(lambda m: m.set_theta(math.sin(0.1 * (self.time - time0)) * 30 * DEGREES))
|
|
self.play(trg_point.animate.move_to(film.get_left()), run_time=10)
|
|
self.play(trg_point.animate.move_to(film.get_right()), run_time=20)
|
|
self.wait(5)
|
|
frame.clear_updaters()
|
|
|
|
# Change wavelength
|
|
trg_wave_number = 16
|
|
|
|
for wave in [obj_wave, ref_wave, comb_wave, exposure]:
|
|
wave.set_wave_number(trg_wave_number)
|
|
wave.set_frequency(0.25 * trg_wave_number)
|
|
|
|
ref_line.insert_n_curves(1000)
|
|
obj_line.insert_n_curves(1000)
|
|
ref_graph.set_stroke(width=1)
|
|
obj_graph.set_stroke(width=1)
|
|
|
|
self.wait(4)
|
|
self.play(
|
|
FadeOut(VGroup(ref_line, ref_graph, obj_line, obj_graph)),
|
|
FadeOut(comb_wave)
|
|
)
|
|
|
|
# Bring objet closer in
|
|
self.play(
|
|
frame.animate.reorient(-85, -9, 0, (0.65, -0.24, -0.14), 6.52),
|
|
run_time=4
|
|
)
|
|
exposure.point_sources.set_points([OUT, 1001 * OUT])
|
|
exposure.point_sources.set_radius(0)
|
|
|
|
mid_line = Line()
|
|
mid_line.set_stroke(TEAL, 2)
|
|
|
|
def get_film_point():
|
|
x, y, _ = source_point.get_center()
|
|
z = film.get_z()
|
|
return np.array([x, y, z])
|
|
|
|
mid_line.add_updater(lambda m: m.set_points_as_corners([source_point.get_center(), get_film_point()]))
|
|
|
|
def get_dist_label():
|
|
label = DecimalNumber(mid_line.get_length(), font_size=24)
|
|
label.set_backstroke(BLACK, 3)
|
|
label.rotate(PI / 2, DOWN)
|
|
label.next_to(mid_line, UP, SMALL_BUFF)
|
|
return label
|
|
|
|
dist_label = always_redraw(get_dist_label)
|
|
|
|
self.play(
|
|
ShowCreation(mid_line, suspend_mobject_updating=True),
|
|
VFadeIn(dist_label),
|
|
)
|
|
|
|
for vect in [2 * IN, 4 * OUT, 2 * IN]:
|
|
self.play(
|
|
source_point.animate.shift(vect),
|
|
exposure.point_sources.animate.shift(vect),
|
|
run_time=3
|
|
)
|
|
self.wait()
|
|
|
|
# Move in 3d
|
|
axes = ThreeDAxes()
|
|
axes.match_width(film)
|
|
axes.move_to(film)
|
|
mid_line.set_z_index(1)
|
|
dist_label.clear_updaters()
|
|
|
|
self.play(
|
|
Write(axes, lag_ratio=0.01),
|
|
frame.animate.reorient(-44, -21, 0, (0.09, -0.06, -1.4), 7.22),
|
|
exposure.animate.set_opacity(0.5),
|
|
FadeOut(dist_label),
|
|
run_time=3
|
|
)
|
|
|
|
frame.add_ambient_rotation(2 * DEGREES)
|
|
exposure.point_sources.add_updater(lambda m: m.move_to(source_point, IN))
|
|
points = [RIGHT, RIGHT + IN, 3 * LEFT + IN, 3 * LEFT + 2 * OUT, 2 * OUT + 3 * RIGHT + 2 * UP, UR, OUT]
|
|
for point in points:
|
|
self.play(source_point.animate.move_to(point), run_time=2)
|
|
self.wait()
|
|
frame.clear_updaters()
|
|
self.play(
|
|
FadeOut(axes),
|
|
FadeOut(mid_line),
|
|
FadeOut(source_point),
|
|
)
|
|
|
|
# Shine the reference through it
|
|
ref_wave.set_opacity(0.75)
|
|
ref_wave.set_wave_number(4.0)
|
|
ref_wave.set_frequency(1.0)
|
|
ref_wave.unpause()
|
|
|
|
self.add(exposure, ref_wave)
|
|
self.play(
|
|
FadeIn(ref_wave, time_span=(0, 2)),
|
|
frame.animate.reorient(0, -24, 0, (0.08, -0.07, -1.39), 7.22),
|
|
run_time=5
|
|
)
|
|
self.wait(4)
|
|
|
|
# Go to other side
|
|
frame.reorient(-171, -18, 0, (0.12, -0.21, -1.33), 9.31)
|
|
self.remove(film)
|
|
self.add(exposure, plate)
|
|
self.play(
|
|
frame.animate.reorient(0, -16, 0, (-3.06, -0.18, -3.19), 1.49),
|
|
run_time=10,
|
|
)
|
|
self.wait(4)
|
|
|
|
def get_round_exposure(self, exposure, radius=1.0, n_pieces=128):
|
|
d_theta = TAU / n_pieces
|
|
vects = [rotate_vector(RIGHT, theta) for theta in np.linspace(0, TAU, n_pieces + 1)]
|
|
result = Group(
|
|
exposure.copy().set_points([ORIGIN, v1, v2])
|
|
for v1, v2 in zip(vects, vects[1:])
|
|
)
|
|
result.set_width(radius)
|
|
result.move_to(exposure)
|
|
return result
|
|
|
|
self.add(round_exposure)
|
|
|
|
def get_3d_waves(self, wave, x_range=(-4, 4, 0.5), opacity=0.25):
|
|
waves = Group(
|
|
wave.copy().rotate(PI / 2, OUT).move_to(x * RIGHT)
|
|
for x in np.arange(*x_range)
|
|
)
|
|
waves.set_opacity(opacity)
|
|
cam_point = self.frame.get_implied_camera_location()
|
|
waves.sort(lambda p: -get_norm(p - cam_point))
|
|
return waves
|
|
|
|
|
|
class ShowEffectOfChangedReferenceAngle(InteractiveScene):
|
|
def construct(self):
|
|
# Create object and reference wave
|
|
frame = self.frame
|
|
axes = ThreeDAxes()
|
|
self.set_floor_plane("xz")
|
|
|
|
wave_width = 100
|
|
wave_number = 10
|
|
frequency = 1
|
|
|
|
source_points = GlowDots([OUT, 10 * OUT], color=WHITE, radius=0.0)
|
|
wave = LightWaveSlice(source_points)
|
|
|
|
wave.set_width(wave_width)
|
|
wave.rotate(PI / 2, RIGHT, about_point=ORIGIN)
|
|
wave.center()
|
|
wave.set_wave_number(wave_number)
|
|
wave.set_frequency(frequency)
|
|
wave.set_max_amp(1.5)
|
|
wave.set_decay_factor(0.5)
|
|
|
|
# Add film
|
|
plate_border = Rectangle(16, 9)
|
|
plate_border.set_fill(BLACK, 0)
|
|
plate_border.set_stroke(WHITE, 2)
|
|
plate = Square3D()
|
|
plate.set_color(BLACK, 0.9)
|
|
plate.replace(plate_border, stretch=True)
|
|
plate.set_shading(0.1, 0.1, 0)
|
|
exposure = LightIntensity(source_points)
|
|
exposure.set_decay_factor(0)
|
|
exposure.set_wave_number(wave_number)
|
|
exposure.replace(plate_border, stretch=True).shift(1e-2 * OUT)
|
|
exposure.set_color(WHITE, 0.85)
|
|
|
|
plate_group = Group(plate_border, plate)
|
|
film = Group(plate_group, exposure)
|
|
film.set_height(4)
|
|
film.set_z(-2)
|
|
|
|
film_label = Text("Film")
|
|
film_label.next_to(plate_group, UP)
|
|
film_label.set_backstroke(BLACK, 3)
|
|
film_label.set_z_index(1)
|
|
|
|
source_dot = GlowDot(source_points.get_points()[0], color=WHITE, radius=0.5)
|
|
|
|
self.add(film, exposure, wave, source_points, source_dot)
|
|
|
|
# Add reference wave lines
|
|
plate_border.insert_n_curves(max(25 - plate_border.get_num_curves(), 0))
|
|
film_points = np.array([plate_border.pfp(a) for a in np.linspace(0, 1, 500)])
|
|
ref_lines = Line().replicate(len(film_points))
|
|
ref_lines.set_stroke(GREEN_SCREEN, width=1, opacity=0.25)
|
|
|
|
def update_ref_lines(lines):
|
|
for line, point in zip(lines, film_points):
|
|
line.set_points_as_corners([source_points.get_points()[1], point])
|
|
return lines
|
|
|
|
ref_lines.add_updater(update_ref_lines)
|
|
|
|
self.add(ref_lines)
|
|
|
|
# Move reference wave
|
|
frame.reorient(-52, -29, 0, (1.21, -0.31, 1.52), 9.28)
|
|
self.wait()
|
|
self.play(
|
|
Rotate(source_points, 60 * DEGREES, axis=UP, about_point=source_points.get_points()[0], run_time=5),
|
|
frame.animate.reorient(-48, -46, 0, (1.17, -0.41, 1.55), 9.28),
|
|
run_time=5
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class DoubleSlit(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Show a diffraction grating
|
|
frame = self.frame
|
|
full_width = 40
|
|
|
|
n_slit_wall = self.get_wall_with_slits(16, spacing=1.0, total_width=full_width)
|
|
n_slit_wall.move_to(0.5 * IN, IN)
|
|
n_slit_wall.save_state()
|
|
n_slit_wall.arrange(RIGHT, buff=0)
|
|
n_slit_wall.move_to(n_slit_wall.saved_state)
|
|
|
|
in_wave = self.get_plane_wave()
|
|
in_wave.set_opacity(0.85)
|
|
in_wave.set_width(full_width)
|
|
in_wave.move_to(ORIGIN, UP)
|
|
|
|
line = Line(0.5 * IN + 16 * DOWN + 0.5 * OUT, 0.5 * IN + 0.5 * OUT)
|
|
graph = self.get_graph_over_wave(line, in_wave)
|
|
|
|
self.add(graph)
|
|
self.add(n_slit_wall)
|
|
self.add(in_wave)
|
|
|
|
frame.reorient(-31, 67, 0, (-3.1, 1.32, -1.12), 15.89)
|
|
self.play(
|
|
frame.animate.reorient(33, 65, 0, (1.24, 1.09, -0.39), 10.01),
|
|
UpdateFromAlphaFunc(
|
|
graph,
|
|
lambda m, a: m.set_stroke(width=3 * clip(there_and_back_with_pause(2 * a, 0.7), 0, 1)),
|
|
),
|
|
Restore(n_slit_wall, time_span=(9, 12)),
|
|
run_time=15
|
|
)
|
|
|
|
# Preview the other side
|
|
sources = self.get_point_sources_from_wall(n_slit_wall)
|
|
sources.set_opacity(0)
|
|
out_wave = LightWaveSlice(sources)
|
|
out_wave.set_max_amp(1)
|
|
out_wave.set_opacity(0.85)
|
|
out_wave.set_decay_factor(0.5)
|
|
out_wave.set_width(full_width * 2.5)
|
|
out_wave.move_to(ORIGIN, DOWN)
|
|
|
|
self.add(sources)
|
|
self.play(
|
|
frame.animate.reorient(1, 49, 0, (-0.02, 3.62, 0.61), 11.96),
|
|
FadeIn(out_wave, time_span=(0, 2), suspend_mobject_updating=False),
|
|
run_time=10
|
|
)
|
|
self.wait(3)
|
|
|
|
# Change spacing
|
|
wall = n_slit_wall
|
|
wall.target = self.get_wall_with_slits(16, spacing=2 + 0.2 * PI, total_width=2 * full_width)
|
|
wall.target.move_to(wall)
|
|
in_wave.match_width(out_wave)
|
|
in_wave.move_to(ORIGIN, UP)
|
|
|
|
start_arrows, end_arrows = [
|
|
VGroup(
|
|
Tex(R"\leftrightarrow").set_width(0.7 * block.get_width(), stretch=True).next_to(block, OUT)
|
|
for block in group[1:-1]
|
|
).rotate(30 * DEGREES, RIGHT).set_backstroke(BLACK, 5)
|
|
for group in [wall, wall.target]
|
|
]
|
|
|
|
self.play(FadeIn(start_arrows))
|
|
self.play(
|
|
Transform(start_arrows, end_arrows),
|
|
MoveToTarget(wall),
|
|
sources.animate.match_points(self.get_point_sources_from_wall(wall.target)),
|
|
# frame.animate.reorient(0, 40, 0, (-0.61, 5.11, 1.62), 24.87),
|
|
run_time=5
|
|
)
|
|
self.play(FadeOut(start_arrows))
|
|
|
|
out_wave.set_width(500, about_edge=DOWN)
|
|
self.play(
|
|
frame.animate.reorient(0, 52, 0, (0.91, 35.42, 33.91), 102.49),
|
|
run_time=16
|
|
)
|
|
|
|
# Reduce to one slit
|
|
single_slit_wall = self.get_wall_with_slits(1)
|
|
single_slit_wall.move_to(wall)
|
|
source = self.get_point_sources_from_wall(single_slit_wall)
|
|
source.set_radius(0.5)
|
|
radial_wave = LightWaveSlice(source)
|
|
radial_wave.set_width(full_width)
|
|
radial_wave.move_to(ORIGIN, DOWN)
|
|
|
|
self.play(
|
|
frame.animate.reorient(11, 65, 0, (-0.03, 0.06, 0.14), 8.66),
|
|
run_time=3
|
|
)
|
|
self.add(single_slit_wall, in_wave)
|
|
self.play(
|
|
FadeOut(wall, scale=0.9),
|
|
FadeOut(out_wave, suspend_mobject_updating=False),
|
|
FadeIn(single_slit_wall),
|
|
)
|
|
self.wait(3)
|
|
|
|
self.play(
|
|
frame.animate.reorient(0, 9, 0, (-0.04, 1.61, 0.15), 8.66),
|
|
FadeIn(radial_wave, suspend_mobject_updating=False),
|
|
run_time=3
|
|
)
|
|
self.wait(2)
|
|
single_slit_wall.set_z_index(1)
|
|
self.play(
|
|
FadeIn(source),
|
|
single_slit_wall.animate.set_opacity(0.1),
|
|
in_wave.animate.set_opacity(0.1),
|
|
)
|
|
self.wait(6)
|
|
|
|
# Setup for sine waves
|
|
def field_func(points):
|
|
result = np.zeros_like(points)
|
|
result[:, 2] = 0.5 * radial_wave.wave_func(points)
|
|
return result
|
|
|
|
# Expose some film
|
|
film_shape = (12, 6)
|
|
film = Rectangle(*film_shape)
|
|
film.set_fill(GREY_E, 1)
|
|
film.set_stroke(WHITE, 1)
|
|
film.rotate(PI / 2, RIGHT)
|
|
film.move_to(source).set_y(5)
|
|
|
|
exposure = LightIntensity(source, shape=film_shape)
|
|
exposure.rotate(PI / 2, RIGHT)
|
|
exposure.move_to(film)
|
|
exposure.set_color(GREEN_SCREEN)
|
|
exposure.set_decay_factor(3.5)
|
|
exposure.set_max_amp(0.005)
|
|
exposure.set_opacity(1e-3)
|
|
|
|
radial_wave.set_z_index(1)
|
|
|
|
single_slit_wall.set_opacity(1)
|
|
single_slit_wall.set_depth(1.5, about_edge=IN)
|
|
|
|
self.play(
|
|
FadeOut(source),
|
|
FadeIn(film, shift=5 * IN),
|
|
FadeIn(exposure, shift=5 * IN),
|
|
FadeIn(single_slit_wall),
|
|
in_wave.animate.set_opacity(0.85).set_time_rate(1.0).set_anim_args(suspend_mobject_updating=False),
|
|
radial_wave.animate.set_time_rate(1.0).set_anim_args(suspend_mobject_updating=False),
|
|
frame.animate.reorient(-19, 68, 0, (-2.38, 4.22, 0.53), 10.86),
|
|
run_time=3
|
|
)
|
|
self.play(exposure.animate.set_opacity(1))
|
|
self.wait(3)
|
|
|
|
# Wave to various spots
|
|
exposure_glow = GlowDot(color=GREEN_SCREEN)
|
|
exposure_glow.move_to(film.get_center())
|
|
globals().update(locals())
|
|
line = Line(stroke_color=TEAL)
|
|
line.add_updater(lambda l: l.put_start_and_end_on(
|
|
source.get_center(), exposure_glow.get_center()
|
|
))
|
|
graph = self.get_graph_over_wave(line, radial_wave, scale_factor=0.2)
|
|
graph.set_stroke(WHITE, 2, 1)
|
|
line.set_stroke(opacity=0)
|
|
|
|
graph.set_z_index(0)
|
|
line.set_stroke(TEAL, 2, 1)
|
|
self.add(line, graph, radial_wave)
|
|
self.play(
|
|
VFadeIn(graph),
|
|
VFadeIn(line),
|
|
FadeIn(exposure_glow),
|
|
frame.animate.reorient(-46, 65, 0, (-1.12, 3.76, 0.49), 6.83),
|
|
run_time=3
|
|
)
|
|
self.wait(3)
|
|
self.play(exposure_glow.animate.shift(5 * RIGHT).set_opacity(0.5), run_time=3)
|
|
self.play(
|
|
radial_wave.animate.set_decay_factor(1.0).set_max_amp(0.75).set_anim_args(suspend_mobject_updating=False),
|
|
run_time=2,
|
|
)
|
|
self.play(
|
|
frame.animate.reorient(9, 59, 0, (0.54, 4.02, 0.04), 8.32),
|
|
run_time=12,
|
|
)
|
|
self.wait(4)
|
|
source.match_points(self.get_point_sources_from_wall(single_slit_wall))
|
|
|
|
# Change to double slit
|
|
two_slit_wall = self.get_wall_with_slits(2, spacing=3.0, depth=single_slit_wall.get_depth())
|
|
two_slit_wall.move_to(single_slit_wall)
|
|
|
|
source.match_points(self.get_point_sources_from_wall(two_slit_wall))
|
|
|
|
self.remove(single_slit_wall, graph, line, exposure_glow)
|
|
self.add(two_slit_wall)
|
|
self.wait(4)
|
|
self.play(
|
|
frame.animate.reorient(-25, 48, 0, (0.23, 4.15, -0.03), 9.80),
|
|
run_time=8
|
|
)
|
|
self.play(
|
|
frame.animate.reorient(28, 50, 0, (0.23, 4.15, -0.03), 9.80),
|
|
run_time=8
|
|
)
|
|
|
|
out_wave = radial_wave # Just rename
|
|
|
|
# Down to two point sources
|
|
source_pair = source
|
|
source1 = source.copy().set_points(source.get_points()[0:1])
|
|
source2 = source.copy().set_points(source.get_points()[1:2])
|
|
|
|
self.play(
|
|
FadeOut(two_slit_wall, shift=2 * IN),
|
|
FadeOut(in_wave, suspend_mobject_updating=False),
|
|
FadeIn(source1),
|
|
FadeIn(source2),
|
|
)
|
|
self.play(
|
|
frame.animate.reorient(0, 68, 0, (0.23, 4.15, -0.03), 9.80),
|
|
run_time=4
|
|
)
|
|
|
|
# Show each individual wave
|
|
wave1 = out_wave.copy().set_sources(source1).shift(1e-3 * OUT)
|
|
wave2 = out_wave.copy().set_sources(source2).shift(1e-3 * IN)
|
|
exp1 = exposure.copy().set_sources(source1).shift(1e-3 * DOWN)
|
|
exp2 = exposure.copy().set_sources(source2).shift(2e-3 * DOWN)
|
|
|
|
self.play(
|
|
FadeOut(source2),
|
|
FadeOut(out_wave, suspend_mobject_updating=False),
|
|
FadeIn(wave1, suspend_mobject_updating=False),
|
|
exposure.animate.set_opacity(0),
|
|
FadeIn(exp1),
|
|
)
|
|
self.wait(3)
|
|
self.add(wave2, wave1)
|
|
self.play(
|
|
FadeOut(source1),
|
|
FadeIn(source2),
|
|
FadeOut(wave1, suspend_mobject_updating=False),
|
|
FadeIn(wave2, suspend_mobject_updating=False),
|
|
FadeOut(exp1),
|
|
FadeIn(exp2),
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
FadeIn(source1),
|
|
FadeOut(wave2, suspend_mobject_updating=False),
|
|
FadeIn(out_wave, suspend_mobject_updating=False),
|
|
FadeOut(exp2),
|
|
exposure.animate.set_opacity(1),
|
|
)
|
|
self.wait(3)
|
|
|
|
# Focus on center point
|
|
exposure_point = GlowDot(color=GREEN_SCREEN)
|
|
exposure_point.move_to(film.get_center())
|
|
|
|
lines = Line().replicate(2)
|
|
lines.set_stroke(TEAL, 2)
|
|
globals().update(locals())
|
|
lines.add_updater(lambda m: m[0].put_start_and_end_on(source1.get_center(), exposure_point.get_center()))
|
|
lines.add_updater(lambda m: m[1].put_start_and_end_on(source2.get_center(), exposure_point.get_center()))
|
|
|
|
graphs = VGroup(
|
|
self.get_graph_over_wave(lines[0], wave1),
|
|
self.get_graph_over_wave(lines[1], wave2),
|
|
)
|
|
|
|
self.play(
|
|
out_wave.animate.pause().set_opacity(0.5).set_anim_args(suspend_mobject_updating=False),
|
|
exposure.animate.set_opacity(0),
|
|
frame.animate.reorient(0, 69, 0, (-0.09, 4.1, -0.16), 7.52),
|
|
FadeIn(exposure_point),
|
|
run_time=3,
|
|
)
|
|
wave1.set_uniform(time=out_wave.uniforms["time"])
|
|
wave2.set_uniform(time=out_wave.uniforms["time"])
|
|
self.play(ShowCreation(lines, lag_ratio=0))
|
|
self.wait()
|
|
self.play(ShowCreation(graphs, lag_ratio=0, run_time=3))
|
|
|
|
# Show combination from a side angle
|
|
self.play(frame.animate.reorient(-80, 83, 0, (-0.09, 4.1, -0.16), 7.52), run_time=3)
|
|
wave1.pause().set_opacity(0)
|
|
wave2.pause().set_opacity(0)
|
|
self.add(wave1, wave2)
|
|
self.play(*(
|
|
wave.animate.unpause().set_anim_args(suspend_mobject_updating=False)
|
|
for wave in [out_wave, wave1, wave2]
|
|
))
|
|
self.wait(4)
|
|
self.play(
|
|
frame.animate.reorient(0, 66, 0, (0.12, 4.03, -0.38), 7.52),
|
|
*(
|
|
wave.animate.pause().set_anim_args(suspend_mobject_updating=False)
|
|
for wave in [out_wave, wave1, wave2]
|
|
),
|
|
run_time=3
|
|
)
|
|
self.wait()
|
|
|
|
# Shift to a destructive point
|
|
self.play(
|
|
exposure_point.animate.shift(0.9 * RIGHT).set_opacity(0.25),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
frame.animate.reorient(-98, 82, 0, (0.27, 4.19, -0.01), 2.93),
|
|
*(
|
|
wave.animate.unpause().set_anim_args(suspend_mobject_updating=False)
|
|
for wave in [out_wave, wave1, wave2]
|
|
),
|
|
run_time=3,
|
|
)
|
|
self.wait(8)
|
|
self.play(
|
|
frame.animate.reorient(-2, 69, 0, (-0.23, 2.91, 0.1), 6.38),
|
|
exposure.animate.set_opacity(1),
|
|
run_time=4
|
|
)
|
|
self.wait(3)
|
|
|
|
# And now over to another constructive point
|
|
self.play(
|
|
exposure_point.animate.shift(0.9 * RIGHT).set_opacity(1),
|
|
run_time=2
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
frame.animate.reorient(81, 88, 0, (-0.23, 2.91, 0.1), 6.38),
|
|
run_time=5,
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
frame.animate.reorient(0, 67, 0, (-0.07, 1.75, 0.9), 6.42),
|
|
*(
|
|
wave.animate.pause().set_anim_args(suspend_mobject_updating=False)
|
|
for wave in [out_wave, wave1, wave2]
|
|
),
|
|
run_time=4,
|
|
)
|
|
self.play(
|
|
exposure_point.animate.shift(5 * LEFT),
|
|
run_time=6
|
|
)
|
|
self.play(
|
|
exposure_point.animate.move_to(film.get_center()),
|
|
run_time=5
|
|
)
|
|
|
|
# Shorten wave length
|
|
trg_color = Color(hsl=(0.7, 0.7, 0.5))
|
|
self.play(
|
|
*(
|
|
wave.animate.set_wave_number(2).set_anim_args(suspend_mobject_updating=False)
|
|
for wave in [out_wave, wave1, wave2, exposure]
|
|
),
|
|
UpdateFromAlphaFunc(
|
|
Point(),
|
|
lambda m, a: exposure.set_color(interpolate_color_by_hsl(GREEN_SCREEN, trg_color, a)),
|
|
remover=True
|
|
),
|
|
exposure_point.animate.set_color(trg_color),
|
|
run_time=6
|
|
)
|
|
|
|
new_out_wave = LightWaveSlice(source_pair)
|
|
new_out_wave.replace(out_wave, stretch=True)
|
|
new_out_wave.set_uniforms(dict(out_wave.uniforms))
|
|
new_out_wave.pause()
|
|
self.remove(out_wave)
|
|
self.add(new_out_wave)
|
|
out_wave = new_out_wave
|
|
|
|
# Pan over various spots
|
|
for wave in [out_wave, wave1, wave2]:
|
|
wave.set_frequency(2)
|
|
|
|
self.play(
|
|
frame.animate.reorient(-80, 68, 0, (-0.22, 2.44, 0.8), 6.42),
|
|
run_time=3
|
|
)
|
|
self.play(
|
|
exposure_point.animate.shift(3 * LEFT),
|
|
rate_func=there_and_back,
|
|
run_time=16,
|
|
)
|
|
self.play(
|
|
FadeOut(lines),
|
|
FadeOut(graphs),
|
|
)
|
|
self.remove(wave1, wave2)
|
|
|
|
# Bring back slits, look over it all
|
|
self.play(out_wave.animate.unpause().set_opacity(1).set_anim_args(suspend_mobject_updating=False))
|
|
new_in_wave = self.get_plane_wave()
|
|
new_in_wave.replace(in_wave)
|
|
new_in_wave.set_wave_number(2)
|
|
new_in_wave.set_frequency(2)
|
|
new_in_wave.set_opacity(0.75)
|
|
|
|
self.play(
|
|
FadeIn(new_in_wave, time_span=(0, 2), suspend_mobject_updating=False),
|
|
FadeIn(two_slit_wall, time_span=(0, 1)),
|
|
FadeOut(source1, time_span=(0, 2)),
|
|
FadeOut(source2, time_span=(0, 2)),
|
|
frame.animate.reorient(-25, 62, 0, (-0.82, 2.58, 0.5), 9.13),
|
|
run_time=8
|
|
)
|
|
self.play(
|
|
frame.animate.reorient(30, 60, 0, (-0.48, 2.77, 0.52), 9.13),
|
|
rate_func=there_and_back,
|
|
run_time=24
|
|
)
|
|
|
|
|
|
class FullDiffractionGrating(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Set up the grating
|
|
full_width = 100
|
|
wave_number = 2 + 0.1 * PI # Make it irrational
|
|
frequency = 1
|
|
slit_dist = 1.0
|
|
|
|
frame = self.frame
|
|
frame.reorient(-28, 76, 0, (0, 3.29, -0.61), 8.17)
|
|
|
|
wall = self.get_wall_with_slits(32, spacing=slit_dist, depth=2.0, total_width=full_width)
|
|
wall.move_to(0.5 * IN, IN)
|
|
|
|
in_wave = self.get_plane_wave()
|
|
in_wave.set_wave_number(wave_number)
|
|
in_wave.set_frequency(frequency)
|
|
in_wave.set_shape(full_width, full_width)
|
|
in_wave.set_opacity(0.85)
|
|
in_wave.move_to(ORIGIN, UP)
|
|
|
|
sources = self.get_point_sources_from_wall(wall)
|
|
out_wave = LightWaveSlice(sources, wave_number=wave_number, frequency=frequency)
|
|
out_wave.set_shape(full_width, full_width)
|
|
out_wave.set_opacity(0.25)
|
|
out_wave.set_max_amp(2)
|
|
out_wave.move_to(ORIGIN, DOWN)
|
|
|
|
self.add(wall)
|
|
self.add(in_wave)
|
|
self.add(out_wave)
|
|
|
|
# Label the distance apart
|
|
piece = wall[16]
|
|
brace = Brace(piece, UP)
|
|
brace.rotate(PI / 2, RIGHT)
|
|
brace.next_to(piece, OUT, SMALL_BUFF)
|
|
dist_label = Tex(R"d", font_size=60)
|
|
dist_label.rotate(PI / 2, RIGHT)
|
|
dist_label.next_to(brace, OUT, SMALL_BUFF)
|
|
VGroup(brace, dist_label).set_backstroke(BLACK, 5)
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeIn(dist_label, 0.25 * OUT),
|
|
frame.animate.reorient(11, 72, 0, (-1.12, 5.34, -0.64), 11.13).set_anim_args(run_time=8),
|
|
)
|
|
dist_label.add(brace)
|
|
|
|
# Show model as an array of point sources
|
|
sources.set_radius(0.5)
|
|
wall.save_state()
|
|
wall.target = wall.generate_target()
|
|
wall.target.stretch(0.05, dim=2, about_point=ORIGIN)
|
|
wall.target.stretch(0.5, dim=1, about_point=ORIGIN)
|
|
|
|
dist_label.set_z_index(1)
|
|
self.play(
|
|
frame.animate.reorient(0, 12, 0, (0, 4.45, -0.62), 11.20),
|
|
dist_label.animate.rotate(PI / 2, LEFT).next_to(piece, UP, SMALL_BUFF),
|
|
MoveToTarget(wall, time_span=(3, 5)),
|
|
FadeIn(sources, time_span=(1, 3)),
|
|
out_wave.animate.set_opacity(1).set_anim_args(suspend_mobject_updating=False),
|
|
in_wave.animate.set_opacity(0.25).set_anim_args(suspend_mobject_updating=False),
|
|
run_time=8,
|
|
)
|
|
self.wait(4)
|
|
|
|
# Show the N graphs
|
|
point_tracker = GlowDot(color=YELLOW, radius=1)
|
|
point_tracker.move_to(8 * UP)
|
|
|
|
def update_lines(lines):
|
|
for line, source_point in zip(lines, sources.get_points()):
|
|
line.put_start_and_end_on(source_point, point_tracker.get_center())
|
|
|
|
lines = Line().replicate(sources.get_num_points())
|
|
lines.set_stroke(YELLOW, 2)
|
|
lines.add_updater(update_lines)
|
|
|
|
individual_sources = Group(
|
|
sources.copy().set_points(sources.get_points()[i:i + 1])
|
|
for i in range(sources.get_num_points())
|
|
)
|
|
|
|
waves = Group(
|
|
out_wave.copy().set_sources(src).set_opacity(0)
|
|
for src in individual_sources
|
|
)
|
|
waves.scale(0)
|
|
|
|
graphs = VGroup(
|
|
self.get_graph_over_wave(line, wave, scale_factor=0.25)
|
|
for line, wave in zip(lines, waves)
|
|
)
|
|
|
|
self.play(
|
|
out_wave.animate.set_opacity(0.2).set_anim_args(suspend_mobject_updating=False),
|
|
FadeOut(dist_label),
|
|
FadeIn(point_tracker),
|
|
)
|
|
self.wait()
|
|
self.add(lines, graphs, out_wave)
|
|
self.add(waves)
|
|
self.play(
|
|
ShowCreation(lines, lag_ratio=0.01, time_span=(0, 2)),
|
|
ShowCreation(graphs, lag_ratio=0.01, suspend_mobject_updating=False, time_span=(0, 2)),
|
|
frame.animate.reorient(1, 57, 0, (-0.17, 5.75, 0.32), 14.54),
|
|
run_time=5
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
FadeOut(lines),
|
|
FadeOut(graphs),
|
|
FadeOut(point_tracker),
|
|
out_wave.animate.set_opacity(0.85).set_anim_args(suspend_mobject_updating=False),
|
|
frame.animate.reorient(0, 42, 0, (0, 5.4, 0.45), 13.00),
|
|
run_time=3,
|
|
)
|
|
self.remove(waves)
|
|
|
|
# Zoom out to large
|
|
out_wave.set_width(500, about_edge=DOWN)
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (0, 95, 0), 200),
|
|
out_wave.animate.set_max_amp(1).set_anim_args(suspend_mobject_updating=False),
|
|
run_time=20
|
|
)
|
|
|
|
# Let it run for a few cycles, we'll use this as an underlay for parts that follow
|
|
self.wait(4)
|
|
|
|
# Highlight the higher order beams
|
|
in_wave.scale(0)
|
|
out_wave.scale(0)
|
|
|
|
beam_point = GlowDot(color=WHITE, radius=3)
|
|
beam_point.move_to(1000 * UP)
|
|
beam_outlines = Line().replicate(2)
|
|
center_beam_line = Line()
|
|
VGroup(beam_outlines, center_beam_line).set_stroke(WHITE, 50)
|
|
globals().update(locals())
|
|
beam_outlines.add_updater(lambda m: m[0].put_start_and_end_on(sources.get_left(), beam_point.get_center()))
|
|
beam_outlines.add_updater(lambda m: m[1].put_start_and_end_on(sources.get_right(), beam_point.get_center()))
|
|
center_beam_line.add_updater(lambda m: m.put_start_and_end_on(sources.get_center(), beam_point.get_center()))
|
|
|
|
theta = math.asin(1.0 / wave_number / slit_dist) # Diffraction equation!
|
|
|
|
self.play(ShowCreation(beam_outlines, lag_ratio=0))
|
|
self.wait(3)
|
|
self.play(
|
|
Rotate(beam_point, -theta, about_point=ORIGIN),
|
|
run_time=1
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Rotate(beam_point, 2 * theta, about_point=ORIGIN),
|
|
run_time=1
|
|
)
|
|
self.wait(8)
|
|
|
|
# Ask about the angle
|
|
v_line = Line(ORIGIN, get_norm(beam_point.get_center()) * UP)
|
|
d_line = Line(ORIGIN, beam_point.get_center())
|
|
VGroup(v_line, d_line).set_stroke(WHITE, 50)
|
|
|
|
arc = og_big_arc = Arc(PI / 2 + theta, -theta, radius=30)
|
|
arc.set_stroke(WHITE, 50)
|
|
theta_sym = Tex(R"\theta")
|
|
theta_sym.set_width(arc.get_width() / 2)
|
|
theta_sym.next_to(arc, UP, buff=2).shift(LEFT)
|
|
|
|
self.remove(beam_outlines)
|
|
self.play(
|
|
TransformFromCopy(beam_outlines[0], d_line),
|
|
TransformFromCopy(beam_outlines[1], d_line),
|
|
)
|
|
self.play(
|
|
TransformFromCopy(d_line, v_line),
|
|
ShowCreation(arc),
|
|
Write(theta_sym),
|
|
)
|
|
self.wait(3)
|
|
|
|
# Analyze central beam
|
|
beam_point.rotate(-theta, about_point=ORIGIN)
|
|
point_tracker.move_to(180 * UP)
|
|
point_tracker.set_radius(8)
|
|
|
|
L_line = Line(ORIGIN, point_tracker.get_center())
|
|
x_line = Line(sources.get_center(), sources.get_left())
|
|
hyp = Line(sources.get_left(), point_tracker.get_center())
|
|
VGroup(L_line, hyp).set_stroke(YELLOW, width=50)
|
|
x_line.set_stroke(WHITE, 50)
|
|
|
|
L_label = Tex("L", font_size=800)
|
|
x_label = Tex("x", font_size=800)
|
|
hyp_label = Tex(R"\sqrt{L^2 + x^2}", font_size=800)
|
|
|
|
L_label.next_to(L_line.pfp(0.4), RIGHT, buff=2)
|
|
L_label.match_color(L_line)
|
|
x_label.next_to(x_line, UP, buff=3)
|
|
hyp_label.next_to(hyp.pfp(0.4), LEFT, buff=2)
|
|
hyp_label.match_color(hyp)
|
|
|
|
self.play(
|
|
FadeIn(beam_outlines),
|
|
FadeOut(d_line),
|
|
FadeOut(v_line),
|
|
FadeOut(arc),
|
|
FadeOut(theta_sym),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
FadeIn(point_tracker),
|
|
out_wave.animate.set_opacity(0.5).set_anim_args(suspend_mobject_updating=False)
|
|
)
|
|
self.play(
|
|
ShowCreation(L_line),
|
|
VFadeIn(L_label),
|
|
FadeOut(beam_outlines),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(L_line, hyp),
|
|
ShowCreation(x_line),
|
|
TransformMatchingStrings(L_label.copy(), hyp_label),
|
|
FadeIn(x_label, shift=3 * LEFT),
|
|
)
|
|
|
|
# Show the approximation (In another scene)
|
|
self.wait(4)
|
|
|
|
# Show all the different lines
|
|
self.play(FadeOut(VGroup(L_label, x_label, hyp_label, x_line, L_line, hyp)))
|
|
lines.set_stroke(YELLOW, 30)
|
|
lines.update()
|
|
|
|
self.play(LaggedStartMap(ShowCreationThenFadeOut, lines, lag_ratio=0.25, run_time=8))
|
|
|
|
# Analyze a point off the center
|
|
new_angle = theta
|
|
lines.set_stroke(width=10)
|
|
arc = Arc(PI / 2 - new_angle, new_angle, radius=30)
|
|
arc.set_stroke(WHITE, 50)
|
|
theta_sym = Tex(R"\theta")
|
|
theta_sym.set_width(0.45 * arc.get_width())
|
|
theta_sym.next_to(arc.get_center(), UP, buff=2).shift(RIGHT)
|
|
d_line = v_line.copy().rotate(-new_angle, about_edge=DOWN)
|
|
question = Text("What about\nover here?")
|
|
question.set_height(15)
|
|
question.rotate(frame.get_phi(), RIGHT)
|
|
question.always.next_to(point_tracker, DR, buff=-2)
|
|
|
|
self.play(
|
|
Rotate(point_tracker, -new_angle, about_point=ORIGIN),
|
|
VFadeIn(question),
|
|
run_time=3
|
|
)
|
|
self.play(ShowCreation(d_line))
|
|
self.play(
|
|
TransformFromCopy(d_line, v_line),
|
|
ShowCreation(arc),
|
|
Write(theta_sym)
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
ShowCreation(lines, lag_ratio=0.01, run_time=2, suspend_mobject_updating=True),
|
|
FadeOut(VGroup(v_line, d_line, arc, theta_sym))
|
|
)
|
|
self.wait(4)
|
|
self.play(
|
|
point_tracker.animate.move_to(1.15 * point_tracker.get_center()),
|
|
run_time=2
|
|
)
|
|
|
|
# Zoom in near the slits again
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (0.0, 2.75, 0.0), 8),
|
|
lines.animate.set_stroke(width=5),
|
|
out_wave.animate.set_opacity(0.1).set_anim_args(suspend_mobject_updating=False),
|
|
in_wave.animate.set_opacity(0.1).set_anim_args(suspend_mobject_updating=False),
|
|
sources.animate.set_radius(0.35),
|
|
run_time=6,
|
|
)
|
|
self.wait(4)
|
|
|
|
# Show individual lines
|
|
lines.suspend_updating()
|
|
line1 = lines[15].copy()
|
|
line2 = lines[16].copy()
|
|
line2.set_stroke(WHITE)
|
|
for line in [line1, line2]:
|
|
line.set_length(8, about_point=line.get_start())
|
|
line.set_stroke(opacity=1)
|
|
line.save_state()
|
|
|
|
lines.target = lines.generate_target()
|
|
lines.target.set_stroke(width=1, opacity=0.5)
|
|
|
|
long_label = Text("Is this longer...")
|
|
short_label = Text("...than this?")
|
|
long_label.match_color(line1)
|
|
long_label.next_to(line1.get_center(), LEFT)
|
|
short_label.next_to(line2.get_center(), RIGHT)
|
|
|
|
self.play(
|
|
MoveToTarget(lines),
|
|
ShowCreation(line1, time_span=(0.5, 1.5)),
|
|
FadeIn(long_label, time_span=(0.5, 1.5))
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(line2),
|
|
TransformMatchingStrings(long_label.copy(), short_label),
|
|
)
|
|
self.wait()
|
|
|
|
# Zoom out and pivot
|
|
for line in [line1, line2]:
|
|
line.put_start_and_end_on(line.get_start(), point_tracker.get_center())
|
|
|
|
tail = TracingTail(line.get_start, time_traced=3.0, stroke_width=(0, 10))
|
|
point_label = TexText(R"Point we're\\analyzing")
|
|
point_label.set_height(1.5 * question.get_height())
|
|
point_label.move_to(question, UL)
|
|
self.remove(question)
|
|
self.add(point_label)
|
|
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (14.74, 92.42, 0.0), 209.77),
|
|
line2.animate.set_stroke(width=50),
|
|
run_time=3
|
|
)
|
|
self.add(tail)
|
|
self.wait(2)
|
|
self.play(
|
|
Rotate(
|
|
line2, -30 * DEGREES,
|
|
about_point=point_tracker.get_center(),
|
|
rate_func=lambda t: wiggle(t, 2),
|
|
run_time=8,
|
|
)
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (0, 1.5, 0.0), 6.0),
|
|
line2.animate.set_stroke(width=5),
|
|
VFadeOut(tail),
|
|
FadeOut(point_label),
|
|
FadeOut(in_wave, suspend_mobject_updating=False),
|
|
FadeOut(out_wave, suspend_mobject_updating=False),
|
|
run_time=3,
|
|
)
|
|
|
|
# Rotate again, as a perp
|
|
tail.add_updater(lambda m: m.set_stroke(width=(0, 5)))
|
|
self.add(tail)
|
|
self.play(
|
|
Rotate(
|
|
line2, -1 * DEGREES,
|
|
about_point=point_tracker.get_center(),
|
|
# rate_func=lambda t: wiggle(t, 2),
|
|
rate_func=there_and_back,
|
|
run_time=5,
|
|
)
|
|
)
|
|
self.wait(3)
|
|
self.remove(tail)
|
|
|
|
# Drop perpendicular
|
|
p1 = line1.get_start()
|
|
p2 = line2.get_start()
|
|
to_point = rotate_vector(UP, -theta)
|
|
foot = p1 + math.sin(theta) * to_point
|
|
|
|
diff_label_group = always_redraw(lambda: self.get_diff_label_group(
|
|
p1=sources.get_points()[15],
|
|
p2=sources.get_points()[16],
|
|
theta=PI / 2 - line1.get_angle()
|
|
))
|
|
diff_label_group.suspend_updating()
|
|
triangle, elbow, altitude, arc, small_theta_sym, diff_segment, brace, d_label = diff_label_group
|
|
|
|
self.play(
|
|
ShowCreation(altitude),
|
|
FadeOut(VGroup(long_label, short_label)),
|
|
frame.animate.reorient(0, 0, 0, (0.5, 0.88, 0.0), 3.8),
|
|
sources.animate.set_radius(0.2),
|
|
)
|
|
self.play(ShowCreation(elbow))
|
|
self.wait()
|
|
|
|
# Compare lengths
|
|
for line in line1, line2:
|
|
line.set_length(5, about_point=line.get_start())
|
|
matched_segment = line2.copy().shift(altitude.get_vector())
|
|
matched_segment.set_color(TEAL)
|
|
label1 = TexText(R"Length of shorter line $\rightarrow$", font_size=24)
|
|
label1.next_to(p2, UR, buff=SMALL_BUFF)
|
|
label1.rotate(PI / 2 - theta, about_point=p2)
|
|
label1.shift(0.1 * to_point)
|
|
label2 = label1.copy()
|
|
label2.match_color(matched_segment)
|
|
label2.shift(matched_segment.get_start() - line2.get_start())
|
|
|
|
diff_label = Text("Difference", font_size=24)
|
|
diff_label.next_to(brace.get_center(), LEFT, buff=0.2).shift(0.15 * UP)
|
|
diff_label.set_color(RED)
|
|
diff_segment.set_stroke(RED, 5)
|
|
|
|
self.play(
|
|
Write(label1, stroke_width=1),
|
|
ShowCreation(line2),
|
|
run_time=1,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(line2, matched_segment),
|
|
line1.animate.set_stroke(width=1)
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStart(
|
|
GrowFromCenter(brace),
|
|
GrowFromCenter(diff_segment),
|
|
Write(diff_label, stroke_width=1),
|
|
lag_ratio=0.2
|
|
))
|
|
self.wait()
|
|
|
|
# Draw the appropriate right triangle
|
|
d_sine_theta = Tex(R"d \cdot \sin(\theta)", font_size=24)
|
|
d_sine_theta.move_to(diff_label, RIGHT)
|
|
|
|
self.add(triangle, elbow, altitude, diff_segment)
|
|
self.play(
|
|
wall.animate.set_height(0.01, stretch=True),
|
|
FadeIn(triangle),
|
|
FadeOut(label1),
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(d_label, 0.25 * DOWN))
|
|
self.wait()
|
|
self.play(
|
|
TransformMatchingStrings(d_label.copy(), d_sine_theta, run_time=1),
|
|
FadeOut(diff_label),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(d_sine_theta[R"\theta"][0], small_theta_sym),
|
|
ShowCreation(arc),
|
|
)
|
|
self.wait()
|
|
|
|
# Lock the leg to match wavelength
|
|
self.remove(in_wave, out_wave)
|
|
self.checkpoint("d*sin(theta)")
|
|
|
|
lambda_label = Tex(R"= \lambda")
|
|
lambda_label[1].set_color(TEAL)
|
|
lambda_label.set_height(0.75 * d_sine_theta.get_height())
|
|
lambda_label.add_updater(lambda m: m.next_to(brace.pfp(0.5), UL, buff=0.025))
|
|
|
|
n_cycles = 8
|
|
sine = FunctionGraph(lambda x: -math.sin(x), x_range=(0, n_cycles * TAU, 0.1))
|
|
sine.set_stroke(TEAL, 1)
|
|
sine.set_width(n_cycles * diff_segment.get_length())
|
|
sine.add_updater(lambda m: m.put_start_and_end_on(
|
|
diff_segment.get_start(), diff_segment.get_end()
|
|
).scale(n_cycles, about_point=diff_segment.get_start()))
|
|
|
|
lock_arrow = Vector(0.5 * DOWN, thickness=2).next_to(brace, UP, buff=0.05)
|
|
lock_label = Text("Consider this\nlocked", font_size=16)
|
|
lock_label.next_to(lock_arrow, UP, SMALL_BUFF)
|
|
|
|
self.play(
|
|
d_sine_theta.animate.scale(0.75).next_to(lambda_label, LEFT, buff=0.05).shift(0.025 * DOWN),
|
|
FadeIn(lambda_label, 0.25 * RIGHT),
|
|
FadeOut(line2),
|
|
FadeOut(matched_segment),
|
|
diff_segment.animate.set_stroke(width=2),
|
|
frame.animate.reorient(0, 0, 0, (0.14, 0.48, 0.0), 3.17).set_anim_args(run_time=2),
|
|
)
|
|
self.play(ShowCreation(sine, rate_func=linear))
|
|
self.wait()
|
|
self.play(
|
|
FadeIn(lock_label),
|
|
GrowArrow(lock_arrow)
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(VGroup(lock_arrow, lock_label)))
|
|
|
|
# Show the other sine waves
|
|
shift_value = p2 - p1
|
|
other_sines = VGroup(sine.copy().shift(x * shift_value) for x in range(-2, 4) if x != 0)
|
|
other_sines.clear_updaters()
|
|
|
|
self.play(ShowCreation(other_sines, lag_ratio=0.25, run_time=4))
|
|
self.wait()
|
|
self.play(FadeOut(other_sines, lag_ratio=0.25, run_time=2))
|
|
|
|
# Change the distance between points
|
|
self.add(diff_label_group)
|
|
diff_label_group.resume_updating()
|
|
|
|
lines.resume_updating()
|
|
line1.clear_updaters()
|
|
line1.add_updater(lambda m: m.match_points(lines[15]))
|
|
line1.resume_updating()
|
|
|
|
def get_dist_point(wavelength):
|
|
dist_to_point = get_norm(point_tracker.get_center())
|
|
d = get_norm(sources.get_points()[1] - sources.get_points()[0])
|
|
angle = math.asin(wavelength / d)
|
|
return rotate_vector(UP, -angle) * dist_to_point
|
|
|
|
self.add(lines, line1, diff_label_group, sine)
|
|
wall_center = sources.get_points()[15]
|
|
scale_factors = [0.5, 2.0, 1.5, 1.0 / 1.5][:-1]
|
|
for scale_factor in scale_factors:
|
|
arrows = VGroup(Vector(0.3 * RIGHT, thickness=1), Vector(0.3 * LEFT, thickness=1))
|
|
arrows.arrange(RIGHT if scale_factor < 1 else LEFT, buff=0.25)
|
|
arrows.always.move_to(d_label)
|
|
self.play(
|
|
UpdateFromFunc(point_tracker, lambda m: m.move_to(get_dist_point(1.0 / wave_number))),
|
|
MaintainPositionRelativeTo(d_sine_theta, lambda_label),
|
|
sources.animate.scale(scale_factor, about_point=wall_center),
|
|
wall.animate.scale(scale_factor, about_point=wall_center),
|
|
FadeIn(arrows, scale=scale_factor, suspend_mobject_updating=False, time_span=(0, 2)),
|
|
run_time=5,
|
|
)
|
|
self.play(FadeOut(arrows))
|
|
|
|
# Show double
|
|
if False: # This was just a temporary insert, not to be run in general
|
|
d_angle2, d_angle3 = [
|
|
angle_of_vector(get_dist_point(n / wave_number)) - angle_of_vector(get_dist_point((n + 1) / wave_number))
|
|
for n in (1, 2)
|
|
]
|
|
sine.clear_updaters()
|
|
new_rhs = Tex(Rf"= 1.00 \lambda", t2c={R"\lambda": TEAL})
|
|
new_rhs.set_height(0.8 * lambda_label.get_height())
|
|
factor = new_rhs.make_number_changeable("1.00")
|
|
factor_tracker = ValueTracker(1.0)
|
|
globals().update(locals())
|
|
new_rhs.add_updater(lambda m: m[1].set_value(factor_tracker.get_value()))
|
|
new_rhs.always.move_to(lambda_label, RIGHT)
|
|
|
|
self.play(
|
|
d_sine_theta.animate.next_to(new_rhs, LEFT, buff=0.05).shift(0.02 * DOWN),
|
|
lambda_label.animate.set_opacity(0),
|
|
FadeIn(new_rhs)
|
|
)
|
|
self.play(
|
|
Rotate(point_tracker, -d_angle2, about_point=ORIGIN),
|
|
Rotate(sine, -d_angle2, about_point=sine.get_start()),
|
|
factor_tracker.animate.set_value(2.0),
|
|
MaintainPositionRelativeTo(d_sine_theta, lambda_label),
|
|
run_time=5,
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Rotate(point_tracker, -d_angle2, about_point=ORIGIN),
|
|
Rotate(sine, -d_angle2, about_point=sine.get_start()),
|
|
factor_tracker.animate.set_value(3.0),
|
|
MaintainPositionRelativeTo(d_sine_theta, lambda_label),
|
|
run_time=5,
|
|
)
|
|
self.wait()
|
|
|
|
# Show the angle match
|
|
self.revert_to_checkpoint("d*sin(theta)")
|
|
|
|
p4 = p2 + 2 * (p2 - p1)
|
|
h_line = Line(p2, p1).scale(3, about_edge=RIGHT)
|
|
h_line.set_stroke(WHITE, 0)
|
|
angle_group = VGroup(triangle, arc, small_theta_sym, h_line).copy()
|
|
angle_group[0].set_opacity(0)
|
|
angle_group.target = angle_group.generate_target()
|
|
angle_group.target.rotate(-PI / 2)
|
|
angle_group.target.move_to(p4, DL)
|
|
angle_group.target[2].rotate(PI / 2).shift(0.01 * UR)
|
|
angle_group.target[3].set_stroke(WHITE, 3)
|
|
angle_group.target.scale(2, about_edge=DL)
|
|
|
|
self.play(
|
|
MoveToTarget(angle_group),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
|
|
# Write conclusion
|
|
conclusion = VGroup(
|
|
Text("Difference in distance:", font_size=36),
|
|
Tex(R"d \cdot \sin(\theta)")
|
|
)
|
|
conclusion.arrange(DOWN)
|
|
conclusion_box = SurroundingRectangle(conclusion, buff=MED_SMALL_BUFF)
|
|
conclusion_box.set_stroke(WHITE, 1)
|
|
conclusion_box.set_fill(BLACK, 1)
|
|
conclusion_group = VGroup(conclusion_box, conclusion)
|
|
conclusion_group.to_corner(UL, buff=SMALL_BUFF)
|
|
conclusion_group.fix_in_frame()
|
|
conclusion_group.set_fill(border_width=0)
|
|
|
|
self.play(
|
|
FadeIn(conclusion_box),
|
|
FadeIn(conclusion[0]),
|
|
TransformFromCopy(d_sine_theta, conclusion[1])
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(conclusion_group))
|
|
|
|
# Zoom out
|
|
out_wave.set_sources(sources.copy().set_points(sources.get_points()[10:-10]))
|
|
out_wave.set_sources(sources)
|
|
out_wave.set_width(800).move_to(ORIGIN, DOWN)
|
|
out_wave.set_opacity(0)
|
|
lines.resume_updating()
|
|
|
|
self.add(out_wave)
|
|
self.play(
|
|
FadeOut(diff_label_group, lag_ratio=0.1, time_span=(0, 1.5)),
|
|
FadeOut(VGroup(d_sine_theta, matched_segment, line1, line2, angle_group), lag_ratio=0.1, time_span=(0, 1.5)),
|
|
out_wave.animate.set_opacity(0.85).set_anim_args(suspend_mobject_updating=False, time_span=(0, 2)),
|
|
frame.animate.reorient(0, 0, 0, (0, 200, 0.0), 400),
|
|
sources.animate.set_radius(0.75),
|
|
lines.animate.set_stroke(width=20).set_anim_args(suspend_mobject_updating=False),
|
|
point_tracker.animate.scale(2.0, about_point=ORIGIN).set_anim_args(time_span=(0, 8)),
|
|
run_time=20,
|
|
rate_func=lambda t: t**6,
|
|
)
|
|
self.wait()
|
|
|
|
# Add back arc label
|
|
arc = always_redraw(lambda: Arc(
|
|
PI / 2, angle_of_vector(point_tracker.get_center()) - PI / 2,
|
|
radius=50,
|
|
stroke_color=WHITE,
|
|
stroke_width=100
|
|
))
|
|
theta_sym.set_height(16)
|
|
globals().update(locals())
|
|
theta_sym.add_updater(lambda m: m.next_to(arc.pfp(0.7), UP, buff=6))
|
|
theta_sym.suspend_updating()
|
|
VGroup(v_line, d_line).set_stroke(WHITE, 150)
|
|
d_line.add_updater(lambda m: m.put_start_and_end_on(ORIGIN, 5 * point_tracker.get_center()))
|
|
self.play(
|
|
ShowCreation(v_line),
|
|
ShowCreation(d_line),
|
|
ShowCreation(arc),
|
|
Write(theta_sym, stroke_width=20),
|
|
run_time=1
|
|
)
|
|
theta_sym.resume_updating()
|
|
self.wait(4)
|
|
|
|
# Change the slit distance zoomed out
|
|
for scale_factor in scale_factors:
|
|
self.play(
|
|
UpdateFromFunc(point_tracker, lambda m: m.move_to(get_dist_point(1.0 / wave_number))),
|
|
sources.animate.scale(scale_factor, about_point=wall_center),
|
|
wall.animate.scale(scale_factor, about_point=wall_center),
|
|
run_time=5,
|
|
)
|
|
self.wait()
|
|
|
|
# Double and triple the angle
|
|
for n in [1, 2]:
|
|
d_angle = angle_of_vector(get_dist_point(n / wave_number)) - angle_of_vector(get_dist_point((n + 1) / wave_number))
|
|
self.play(
|
|
Rotate(point_tracker, -d_angle, about_point=ORIGIN),
|
|
frame.animate.set_height(450),
|
|
run_time=5
|
|
)
|
|
self.wait()
|
|
|
|
def get_diff_label_group(self, p1, p2, theta):
|
|
# Altitude
|
|
to_point = rotate_vector(UP, -theta)
|
|
dist = get_norm(p2 - p1)
|
|
foot = p1 + dist * math.sin(theta) * to_point
|
|
|
|
altitude = DashedLine(p2, foot, dash_length=get_norm(foot - p2) / 39.5)
|
|
elbow = Elbow(width=0.1 * dist, angle=-theta - PI / 2).shift(foot)
|
|
|
|
altitude.set_stroke(WHITE, 2)
|
|
elbow.set_stroke(WHITE, 2)
|
|
|
|
# Triangle
|
|
triangle = Polygon(p1, foot, p2)
|
|
triangle.set_stroke(width=0)
|
|
triangle.set_fill(YELLOW, 0.5)
|
|
d_label = Tex(R"d", font_size=24)
|
|
d_label.next_to(triangle, DOWN, SMALL_BUFF)
|
|
|
|
# Leg
|
|
diff_segment = Line(p1, foot)
|
|
diff_segment.set_stroke(RED, 2)
|
|
brace = VMobject().set_points_as_corners([LEFT, UL, UR, RIGHT])
|
|
brace.set_shape(diff_segment.get_length(), 0.1)
|
|
brace.set_stroke(WHITE, 1)
|
|
# brace = Brace(Line(ORIGIN, 0.75 * RIGHT), UP)
|
|
# brace.set_shape(diff_segment.get_length(), 0.15)
|
|
brace.rotate(PI / 2 - theta)
|
|
brace.move_to(diff_segment).shift(0.1 * rotate_vector(to_point, PI / 2))
|
|
|
|
# Angle label
|
|
arc_rad = min(0.35 * dist, 0.35 * get_norm(foot - p2))
|
|
arc = Arc(PI, -theta, radius=arc_rad).shift(p2)
|
|
arc.set_stroke(WHITE, 2)
|
|
small_theta_sym = Tex(R"\theta")
|
|
small_theta_sym.set_height(0.8 * arc.get_height())
|
|
small_theta_sym.next_to(arc.pfp(0.5), LEFT, buff=0.05)
|
|
|
|
return VGroup(triangle, elbow, altitude, arc, small_theta_sym, diff_segment, brace, d_label)
|
|
|
|
def old(self):
|
|
# Old
|
|
dist_label = DecimalNumber(num_decimal_places=1)
|
|
dist_label.set_height(4)
|
|
globals().update(locals())
|
|
dist_label.add_updater(lambda m: m.next_to(L_line.get_center(), RIGHT, buff=2))
|
|
dist_label.add_updater(lambda m: m.set_value(L_line.get_length()))
|
|
|
|
|
|
class PlaneWaveThroughZonePlate(DiffractionGratingScene):
|
|
def construct(self):
|
|
# Set up the zone plate and object
|
|
frame = self.frame
|
|
wave_number = 4
|
|
frequency = 2.0
|
|
|
|
obj_dot = Group(GlowDot(), TrueDot())
|
|
obj_dot.move_to(4 * RIGHT)
|
|
obj_dot.set_color(WHITE)
|
|
|
|
zone_sources = DotCloud([obj_dot.get_center(), 1002 * RIGHT])
|
|
plate = LightIntensity(zone_sources)
|
|
plate.set_shape(9, 16)
|
|
plate.rotate(PI / 2, UP)
|
|
plate.set_height(8)
|
|
plate.set_color(WHITE, 0.7)
|
|
plate.set_wave_number(24)
|
|
plate.set_decay_factor(0)
|
|
plate_top = plate.copy()
|
|
plate_top.rotate(PI / 2, DOWN)
|
|
plate_top.set_width(0.075, stretch=True)
|
|
plate_top.move_to(plate)
|
|
|
|
ref_wave = self.get_plane_wave(LEFT)
|
|
ref_wave.set_shape(10, plate.get_height())
|
|
ref_wave.set_frequency(frequency)
|
|
ref_wave.set_color(BLUE_C, 0.5)
|
|
ref_wave.set_wave_number(wave_number)
|
|
ref_wave.move_to(plate, LEFT)
|
|
|
|
frame.reorient(19, 77, 0, ORIGIN, 8.00)
|
|
self.add(plate)
|
|
self.add(obj_dot)
|
|
|
|
# Add Number plane
|
|
plane = NumberPlane(x_range=(-10, 10, 1), y_range=(-8, 8, 1.0))
|
|
plane.become(NumberPlane(x_range=(-10, 10, 1), y_range=(-8, 8, 1.0)))
|
|
plane.fade(0.5)
|
|
plane.apply_depth_test()
|
|
self.add(plate, plane)
|
|
|
|
self.play(
|
|
frame.animate.reorient(58, 73, 0, ORIGIN, 8.00),
|
|
Write(plane, stroke_width=3, lag_ratio=0.01, time_span=(2, 6)),
|
|
run_time=6
|
|
)
|
|
|
|
# Draw a line
|
|
film_point = 2 * UP
|
|
line = Line(film_point, obj_dot.get_center())
|
|
line.set_stroke(TEAL, 3)
|
|
|
|
self.play(
|
|
ShowCreation(line),
|
|
frame.animate.reorient(0, 0, 0),
|
|
FadeIn(plate_top),
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
|
|
# Where the object had been
|
|
dash_circle = DashedVMobject(Arc(angle=(23 / 24) * TAU), num_dashes=12)
|
|
dash_circle.set_stroke(YELLOW, 3)
|
|
dash_circle.replace(obj_dot).set_width(0.2)
|
|
for part in dash_circle:
|
|
dash_circle.set_joint_type("no_joint")
|
|
had_been_words = Text("Where the object\nhad been", font_size=36)
|
|
had_been_words.next_to(dash_circle, UP, buff=0, aligned_edge=LEFT)
|
|
|
|
self.play(
|
|
FadeOut(obj_dot),
|
|
Write(dash_circle, stroke_width=3, run_time=1),
|
|
Write(had_been_words, run_time=1),
|
|
)
|
|
self.wait()
|
|
self.play(FadeOut(had_been_words))
|
|
|
|
# Show angle
|
|
theta = -line.get_angle()
|
|
arc = Arc(PI - theta, theta, radius=1)
|
|
arc.shift(obj_dot.get_center())
|
|
h_line = Line(ORIGIN, obj_dot.get_center())
|
|
h_line.set_stroke(WHITE, 2)
|
|
theta_prime_sym = Tex(R"\theta'")
|
|
theta_prime_sym.set_max_height(0.8 * arc.get_height())
|
|
theta_prime_sym.next_to(arc.pfp(0.4), LEFT, SMALL_BUFF)
|
|
|
|
self.play(
|
|
TransformFromCopy(line, h_line),
|
|
ShowCreation(arc),
|
|
Write(theta_prime_sym),
|
|
)
|
|
self.wait()
|
|
|
|
# Set up terms for the calculations for the spacing
|
|
# TODO, consider adding many little lines for all the fringes
|
|
self.remove(plate)
|
|
v_line = Line(ORIGIN, film_point)
|
|
|
|
d_lines = Line(LEFT, RIGHT).replicate(2).set_width(0.3)
|
|
d_lines.set_stroke(WHITE, 2)
|
|
d_lines.arrange(DOWN, buff=0.1)
|
|
d_lines.move_to(film_point, DOWN)
|
|
lil_brace = Brace(Line(ORIGIN, 0.25 * UP), LEFT)
|
|
lil_brace.match_height(d_lines)
|
|
lil_brace.next_to(d_lines, LEFT, buff=0.05)
|
|
big_brace = Brace(Group(d_lines[1], Point(ORIGIN)), LEFT, buff=0)
|
|
big_brace.match_width(lil_brace, about_edge=RIGHT, stretch=True)
|
|
|
|
kw = dict(font_size=42)
|
|
L_label = Tex("L", **kw).next_to(h_line, DOWN, 2 * SMALL_BUFF)
|
|
x_label = Tex("x", **kw).next_to(big_brace, LEFT, SMALL_BUFF)
|
|
d_label = Tex("d", **kw).next_to(lil_brace, LEFT, SMALL_BUFF, aligned_edge=DOWN)
|
|
L_label.set_color(BLUE)
|
|
x_label.set_color(RED)
|
|
VGroup(L_label, x_label, d_label).set_backstroke(BLACK, 5)
|
|
|
|
terms = VGroup(
|
|
d_lines, lil_brace, big_brace,
|
|
L_label, x_label, d_label
|
|
)
|
|
|
|
# Limit to reference beam at just one point
|
|
equations_tex = [
|
|
R"\lambda = \sqrt{L^2 + (x + d)^2} - \sqrt{L^2 + x^2}",
|
|
R"\approx \left(L + \frac{1}{2L}(x + d)^2\right) - \left(L + \frac{1}{2L} x^2\right)",
|
|
R"= \frac{1}{2L}\left(x^2 + 2xd + d^2 - x^2 \right)",
|
|
R"= \frac{1}{2L}\left(2xd + d^2\right)",
|
|
R"\approx d \cdot \frac{x}{L}",
|
|
R"= d \cdot \sin(\theta')",
|
|
]
|
|
equations = VGroup(
|
|
Tex(eq, t2c={R"\lambda": YELLOW, "L": BLUE, "x": RED}, font_size=36)
|
|
for eq in equations_tex
|
|
)
|
|
equations.arrange(DOWN, buff=0.65, aligned_edge=LEFT)
|
|
equations.move_to(7 * LEFT + 5.65 * UP, UL)
|
|
equations.set_backstroke(BLACK, 10)
|
|
|
|
annotations = VGroup(
|
|
Text("The distances between adjacent fringes and\nthe object should differ by one wavelength"),
|
|
TexText(R"First-order Taylor expansion: \\ \quad \\ $\sqrt{L^2 + x} \approx L + \frac{1}{2L} x$"),
|
|
TexText(R"$d$ is small compared to $x$, \\ so $d^2$ is small compared to $xd$"),
|
|
)
|
|
annotations[2].scale(0.8)
|
|
for annotation, i in zip(annotations, [0, 1, 4]):
|
|
arrow = Vector(LEFT)
|
|
arrow.next_to(equations[i], RIGHT)
|
|
annotation.scale(0.75)
|
|
annotation.next_to(arrow, RIGHT)
|
|
annotation.add(arrow)
|
|
annotation.set_color(GREY_A)
|
|
annotations.set_backstroke(BLACK, 5)
|
|
annotations[1][:-1].shift(0.35 * DOWN)
|
|
annotations[1][R"$\sqrt{L^2 + x} \approx L + \frac{1}{2L} x$"].scale(1.25, about_edge=UP).set_fill(WHITE)
|
|
annotations[0].align_to(annotations[1], LEFT)
|
|
annotations[2].shift(0.15 * UP)
|
|
annotations[2][-1].become(Arrow(annotations[2][0].get_left(), equations[3]["d^2"].get_bottom()))
|
|
|
|
braces = VGroup(
|
|
Brace(equations[0][R"\sqrt{L^2 + (x + d)^2}"], UP, SMALL_BUFF),
|
|
Brace(equations[0][R"\sqrt{L^2 + x^2}"], UP, SMALL_BUFF),
|
|
)
|
|
brace_texts = VGroup(
|
|
TexText(R"Dist. to fringe\\at height $(x + d)$", font_size=24).next_to(braces[0], UP, SMALL_BUFF),
|
|
TexText(R"Dist. to fringe\\at height $x$", font_size=24).next_to(braces[1], UP, SMALL_BUFF),
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(terms, lag_ratio=0.1, time_span=(0, 2)),
|
|
Write(equations, time_span=(2, 5)),
|
|
frame.animate.reorient(0, 0, 0, (-0.3, 2.5, 0.0), 9).set_anim_args(run_time=3),
|
|
)
|
|
self.wait()
|
|
self.play(LaggedStart(
|
|
FadeIn(annotations[0]),
|
|
FadeIn(braces),
|
|
FadeIn(brace_texts),
|
|
))
|
|
self.wait()
|
|
self.play(FadeIn(annotations[1]))
|
|
self.play(FadeIn(annotations[2]))
|
|
self.wait()
|
|
|
|
# Reduce down to the key conclusion
|
|
key_equation = Tex(R"d \cdot \sin(\theta') = \lambda", **kw)
|
|
key_equation.next_to(line, UP, MED_LARGE_BUFF)
|
|
key_equation.scale(1.25)
|
|
key_equation.shift(RIGHT + 0.5 * UP)
|
|
|
|
box = SurroundingRectangle(key_equation, buff=MED_SMALL_BUFF)
|
|
box.set_fill(BLACK, 1)
|
|
box.set_stroke(YELLOW, 1)
|
|
|
|
terms.remove(d_label, lil_brace, d_lines)
|
|
|
|
self.add(d_label, lil_brace, d_lines)
|
|
self.play(
|
|
ReplacementTransform(equations[-1][0], key_equation[9], time_span=(0, 2)),
|
|
ReplacementTransform(equations[-1][1:], key_equation[:9], time_span=(0, 2)),
|
|
ReplacementTransform(equations[0][0], key_equation[10], time_span=(0, 2)),
|
|
FadeOut(equations[0][1:], time_span=(1.0, 1.5)),
|
|
FadeOut(equations[1:-1], lag_ratio=0.01, time_span=(1.0, 2.5)),
|
|
FadeOut(annotations, lag_ratio=0.01),
|
|
FadeOut(terms, lag_ratio=0.1, time_span=(1.0, 3.0)),
|
|
FadeOut(h_line),
|
|
FadeOut(braces),
|
|
FadeOut(brace_texts),
|
|
frame.animate.reorient(0, 0, 0, (0, 1, 0), 6).set_anim_args(time_span=(1, 3.5)),
|
|
)
|
|
self.add(box, key_equation)
|
|
self.play(
|
|
Write(box),
|
|
FlashAround(key_equation, buff=MED_SMALL_BUFF, time_width=1.5, run_time=2),
|
|
)
|
|
self.wait()
|
|
|
|
# Smaller slit width
|
|
lil_brace.generate_target()
|
|
lil_brace.target.flip().next_to(d_lines, RIGHT, buff=0.025)
|
|
|
|
arrow = Vector(0.3 * DL, thickness=2)
|
|
arrow.next_to(lil_brace.target, UR, buff=0)
|
|
|
|
new_plate_top = plate_top.copy()
|
|
new_plate_top.set_wave_number(50)
|
|
new_plate_top.save_state()
|
|
new_plate_top.stretch(0, 0)
|
|
|
|
self.play(
|
|
d_label.animate.next_to(arrow.get_start(), UR, 0.5 * SMALL_BUFF),
|
|
GrowArrow(arrow),
|
|
MoveToTarget(lil_brace)
|
|
)
|
|
self.play(
|
|
d_lines.animate.stretch(0.25, 1, about_edge=DOWN),
|
|
lil_brace.animate.scale(0.25, about_edge=DL).set_stroke(WHITE, 1),
|
|
arrow.animate.put_start_and_end_on(arrow.get_start(), arrow.get_end() + 0.05 * LEFT + 0.05 * DOWN),
|
|
plate_top.animate.stretch(0, 0),
|
|
Restore(new_plate_top),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
self.remove(new_plate_top)
|
|
plate_top.become(new_plate_top)
|
|
|
|
# Shine reference beam in
|
|
ref_wave = self.get_beam()
|
|
ref_wave.move_to(film_point, LEFT)
|
|
|
|
out_beams = self.get_triple_beam(film_point, obj_dot.get_center())
|
|
|
|
self.play(GrowFromPoint(ref_wave, film_point + 8 * RIGHT, run_time=2, rate_func=linear))
|
|
self.play(*(
|
|
GrowFromPoint(beam[1], film_point, run_time=2, rate_func=linear)
|
|
for beam in out_beams
|
|
))
|
|
self.wait(4)
|
|
|
|
# Note the matching angle
|
|
upper_arc = arc.copy()
|
|
upper_arc.shift(film_point - obj_dot.get_center())
|
|
theta_sym = Tex(R"\theta", font_size=42)
|
|
theta_sym.next_to(upper_arc.pfp(0.4), LEFT, SMALL_BUFF)
|
|
|
|
self.play(
|
|
ShowCreation(upper_arc),
|
|
Write(theta_sym),
|
|
)
|
|
self.wait(4)
|
|
|
|
# Write the diffraction equation
|
|
key_equation.set_backstroke(BLACK, 8)
|
|
key_equation.generate_target()
|
|
box.generate_target()
|
|
diff_eq = Tex(R"d \cdot \sin(\theta) = \lambda")
|
|
key_equation.target.match_height(diff_eq)
|
|
key_equation.target.next_to([6.5, 5.5, 0], DL, SMALL_BUFF)
|
|
diff_eq.next_to(key_equation.target, DOWN, MED_LARGE_BUFF)
|
|
|
|
box.target.surround(VGroup(key_equation.target, diff_eq))
|
|
box.target.set_opacity(0)
|
|
|
|
diff_eq_label = VGroup(
|
|
Text("Diffraction\nequation", font_size=36),
|
|
Vector(RIGHT),
|
|
)
|
|
diff_eq_label.arrange(RIGHT)
|
|
diff_eq_label.next_to(diff_eq, LEFT)
|
|
|
|
VGroup(diff_eq, diff_eq_label).set_backstroke(BLACK, 8)
|
|
|
|
theta_sym_copy = theta_sym.copy()
|
|
theta_sym_copy.set_backstroke()
|
|
|
|
self.play(
|
|
frame.animate.reorient(0, 0, 0, (0.0, 2, 0.0), 8.00),
|
|
MoveToTarget(box),
|
|
MoveToTarget(key_equation),
|
|
Transform(theta_sym_copy, diff_eq[R"\theta"][0]),
|
|
run_time=2
|
|
)
|
|
self.play(
|
|
Write(diff_eq),
|
|
FadeIn(diff_eq_label[0], lag_ratio=0.1),
|
|
GrowArrow(diff_eq_label[1]),
|
|
)
|
|
self.remove(theta_sym_copy)
|
|
self.wait(2)
|
|
|
|
# Write implication
|
|
implication = VGroup(Tex(R"\Downarrow"), Tex(R"\theta = \theta'"))
|
|
implication.arrange(DOWN)
|
|
implication.next_to(diff_eq, DOWN)
|
|
implication.set_backstroke(width=5)
|
|
|
|
self.play(Write(implication))
|
|
self.wait()
|
|
self.play(
|
|
Transform(theta_sym.copy(), theta_prime_sym, remover=True),
|
|
Transform(upper_arc.copy(), arc, remover=True),
|
|
run_time=2
|
|
)
|
|
self.wait(4)
|
|
|
|
# Move film point around
|
|
film_dot = Point(film_point)
|
|
globals().update(locals())
|
|
|
|
line.f_always.put_start_and_end_on(film_dot.get_center, obj_dot.get_center)
|
|
|
|
ref_wave.always.match_y(film_dot)
|
|
|
|
def update_out_beams(beams):
|
|
beams.become(self.get_triple_beam(
|
|
film_dot.get_center(),
|
|
obj_dot.get_center(),
|
|
))
|
|
for beam in beams:
|
|
beam[1].set_uniform(time=self.time)
|
|
|
|
globals().update(locals())
|
|
out_beams.clear_updaters()
|
|
out_beams.add_updater(update_out_beams)
|
|
|
|
self.add(out_beams)
|
|
self.play(film_dot.animate.move_to(film_point))
|
|
|
|
arc.add_updater(lambda m: m.become(
|
|
Arc(PI, line.get_angle()).shift(obj_dot.get_center())
|
|
))
|
|
upper_arc.add_updater(lambda m: m.match_points(arc).shift(
|
|
film_dot.get_center() - obj_dot.get_center()
|
|
))
|
|
theta_prime_sym.add_updater(
|
|
lambda m: m.set_height(min(0.8 * arc.get_height(), 0.35)).next_to(arc.pfp(0.6), LEFT, SMALL_BUFF)
|
|
)
|
|
theta_sym.add_updater(lambda m: m.replace(theta_prime_sym[0]).shift(
|
|
film_dot.get_center() - obj_dot.get_center()
|
|
))
|
|
|
|
d_group = VGroup(d_label, arrow, d_lines, lil_brace)
|
|
|
|
self.play(
|
|
FadeOut(d_group, time_span=(0, 1)),
|
|
film_dot.animate.set_y(1),
|
|
run_time=3
|
|
)
|
|
self.play(film_dot.animate.set_y(3.5), run_time=5)
|
|
self.play(film_dot.animate.set_y(0.5), run_time=6)
|
|
self.play(film_dot.animate.set_y(3.5), run_time=6)
|
|
|
|
# Show zone plate and observer
|
|
equaiton_group = VGroup(box, key_equation, diff_eq, diff_eq_label, implication)
|
|
|
|
randy = Randolph(height=2)
|
|
randy.move_to(4 * LEFT, DOWN)
|
|
|
|
plate.set_opacity(0.5)
|
|
plate.set_wave_number(plate_top.uniforms["wave_number"])
|
|
|
|
self.add(plate, plane)
|
|
self.play(
|
|
FadeOut(equaiton_group, lag_ratio=0.1, time_span=(0, 2)),
|
|
film_dot.animate.set_y(1.0),
|
|
FadeOut(plate_top, time_span=(0, 1)),
|
|
FadeIn(randy, time_span=(1, 3)),
|
|
frame.animate.reorient(-33, 43, 0, (-2.43, 0.5, -0.12), 9.60),
|
|
run_time=5
|
|
)
|
|
self.play(randy.change("pondering", obj_dot))
|
|
self.play(Blink(randy))
|
|
self.wait(3)
|
|
|
|
# Show full reference wave
|
|
big_ref_wave = self.get_3d_ref_wave(plate)
|
|
|
|
self.add(big_ref_wave, plate)
|
|
self.play(
|
|
FadeIn(big_ref_wave),
|
|
FadeOut(ref_wave),
|
|
frame.animate.reorient(-40, 67, 0, (-2.43, 0.5, -0.12), 9.60).set_anim_args(run_time=3)
|
|
)
|
|
self.wait(2)
|
|
|
|
# Show many beams off the plate
|
|
mid_line_points = DotCloud().to_grid(25, 1)
|
|
mid_line_points.replace(plate, dim_to_match=1)
|
|
mid_line_points.rotate(PI)
|
|
plate_points = DotCloud().to_grid(15, 11)
|
|
dense_plate_points = DotCloud().to_grid(60, 40)
|
|
for dot_cloud in [plate_points, dense_plate_points]:
|
|
dot_cloud.rotate(PI / 2, UP)
|
|
dot_cloud.replace(plate, stretch=True)
|
|
|
|
mid_lines_out = self.get_radiating_lines(mid_line_points, obj_dot)
|
|
lines_out = self.get_radiating_lines(plate_points, obj_dot)
|
|
dense_lines_out = self.get_radiating_lines(dense_plate_points, obj_dot)
|
|
ghost_lines = self.get_ghost_lines(mid_line_points, obj_dot)
|
|
dense_ghost_lines = self.get_ghost_lines(dense_plate_points, obj_dot)
|
|
|
|
out_beams.clear_updaters()
|
|
self.play(
|
|
FadeOut(out_beams),
|
|
FadeOut(VGroup(theta_sym, theta_prime_sym, upper_arc, arc, line)),
|
|
ShowCreation(lines_out, lag_ratio=0.01, run_time=4),
|
|
frame.animate.reorient(-93, 62, 0, (-2.43, 0.5, -0.12), 9.60).set_anim_args(run_time=5)
|
|
)
|
|
self.play(
|
|
FadeOut(lines_out, time_span=(1, 2)),
|
|
FadeOut(big_ref_wave),
|
|
FadeIn(mid_lines_out, time_span=(1, 2)),
|
|
frame.animate.to_default_state(),
|
|
run_time=3,
|
|
)
|
|
self.play(LaggedStartMap(ShowCreation, ghost_lines, lag_ratio=0.01))
|
|
self.wait()
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
|
|
# Move character around
|
|
def get_view_point():
|
|
eye_point = randy.eyes[1].get_center()
|
|
obj_point = obj_dot.get_center()
|
|
vect = obj_point - eye_point
|
|
alpha = 1.0 - (obj_point[0] / vect[0])
|
|
return eye_point + alpha * vect
|
|
|
|
def update_lines(lines):
|
|
view_point = get_view_point()
|
|
min_dist = 2.5 * get_norm(lines[0].get_start() - lines[1].get_start())
|
|
for line in lines:
|
|
dist = get_norm(line.get_start() - view_point)
|
|
alpha = clip(inverse_interpolate(min_dist, 0, dist), 0, 1)
|
|
line.set_stroke(opacity=interpolate(0, 1, alpha))
|
|
|
|
screen_dot = GlowDot(radius=0.5)
|
|
screen_dot.f_always.move_to(get_view_point)
|
|
|
|
mid_lines_out.add_updater(update_lines)
|
|
ghost_lines.add_updater(update_lines)
|
|
|
|
randy.always.look_at(obj_dot)
|
|
|
|
self.play(FadeIn(screen_dot))
|
|
self.add(mid_lines_out, ghost_lines)
|
|
for y in [-2.8, 2.2]:
|
|
self.play(randy.animate.set_y(y), run_time=4)
|
|
|
|
# Movement in 3d
|
|
dense_lines_out.clear_updaters()
|
|
dense_ghost_lines.clear_updaters()
|
|
dense_lines_out.add_updater(update_lines)
|
|
dense_ghost_lines.add_updater(update_lines)
|
|
|
|
glass = Rectangle()
|
|
glass.rotate(PI / 2, UP)
|
|
glass.replace(plate, stretch=True)
|
|
glass.set_stroke(WHITE, 1)
|
|
glass.set_fill(BLACK, 0.25)
|
|
|
|
self.remove(plate)
|
|
self.add(glass, randy)
|
|
self.play(
|
|
FadeIn(glass, time_span=(0, 1)),
|
|
FadeOut(mid_lines_out, time_span=(1, 2)),
|
|
FadeOut(ghost_lines, time_span=(1, 2)),
|
|
FadeIn(dense_lines_out, time_span=(1, 2)),
|
|
FadeIn(dense_ghost_lines, time_span=(1, 2)),
|
|
randy.animate.rotate(PI / 2, RIGHT).shift(0.2 * (IN + DOWN)),
|
|
frame.animate.reorient(-48, 71, 0, (0.53, -0.56, 0.06), 10.86),
|
|
run_time=3
|
|
)
|
|
|
|
frame.add_ambient_rotation(1 * DEGREES)
|
|
for (y, z) in [(-3, 2), (-2, -1.5), (-1, 1), (2, 2), (1.1, 1.1)]:
|
|
self.play(randy.animate.set_y(y).set_z(z), run_time=3)
|
|
|
|
# Reintroduce the beams
|
|
frame.clear_updaters()
|
|
dense_lines_out.clear_updaters()
|
|
dense_ghost_lines.clear_updaters()
|
|
self.play(
|
|
FadeOut(dense_lines_out, time_span=(0, 1)),
|
|
FadeOut(dense_ghost_lines, time_span=(0, 1)),
|
|
FadeOut(screen_dot, time_span=(0, 1)),
|
|
FadeOut(glass, time_span=(2, 3)),
|
|
FadeIn(plate_top, time_span=(2.0, 3)),
|
|
randy.animate.rotate(PI / 2, LEFT).move_to(4 * LEFT, DOWN),
|
|
frame.animate.reorient(0, 0, 0, ORIGIN, FRAME_HEIGHT),
|
|
run_time=3
|
|
)
|
|
self.play(GrowFromPoint(ref_wave, ref_wave.get_right(), rate_func=linear))
|
|
out_beams.clear_updaters()
|
|
self.play(GrowFromPoint(out_beams, film_dot.get_center(), rate_func=linear))
|
|
out_beams.add_updater(update_out_beams)
|
|
self.wait(3)
|
|
|
|
# Move film point
|
|
self.play(
|
|
film_dot.animate.match_y(randy.eyes),
|
|
run_time=2
|
|
)
|
|
self.wait(6)
|
|
self.play(
|
|
randy.animate.move_to(2.4 * LEFT),
|
|
run_time=2
|
|
)
|
|
|
|
# Highlight other first order beam
|
|
self.remove(out_beams)
|
|
out_beams = self.get_triple_beam(film_dot.get_center(), obj_dot.get_center())
|
|
self.add(out_beams)
|
|
self.play(
|
|
out_beams[0][1].animate.set_opacity(0.25),
|
|
out_beams[1][1].animate.set_opacity(0.25),
|
|
)
|
|
randy.clear_updaters()
|
|
self.play(randy.change("confused", film_point))
|
|
self.play(Blink(randy))
|
|
self.wait(4)
|
|
self.play(FadeOut(randy))
|
|
|
|
# Add all other first order beams
|
|
out_beams.add_updater(update_out_beams)
|
|
out_beams.add_updater(lambda m: m[0][1].set_opacity(0.25))
|
|
out_beams.add_updater(lambda m: m[1][1].set_opacity(0.25))
|
|
|
|
conj_lines = VGroup(
|
|
Line(point, obj_dot.get_center())
|
|
for point in mid_line_points.get_points()
|
|
)
|
|
conj_lines.flip(UP, about_point=ORIGIN)
|
|
conj_lines.set_stroke(YELLOW, 1)
|
|
conj_lines.sort(lambda p: -p[1])
|
|
|
|
self.add(out_beams)
|
|
self.play(film_dot.animate.set_y(4), run_time=4)
|
|
self.play(
|
|
film_dot.animate.set_y(-4),
|
|
FadeIn(conj_lines, lag_ratio=0.25, run_time=4),
|
|
run_time=5,
|
|
)
|
|
self.play(film_dot.animate.set_y(3.5), run_time=8)
|
|
self.play(film_dot.animate.set_y(2), run_time=8)
|
|
self.wait()
|
|
self.play(FadeOut(conj_lines))
|
|
|
|
# Show higher order beams
|
|
theta = -angle_of_vector(obj_dot.get_center() - film_dot.get_center())
|
|
wave_number = 250
|
|
wavelength = 1.0 / wave_number
|
|
spacing = wavelength / math.sin(theta)
|
|
n_sources = 32
|
|
sources = DotCloud().to_grid(n_sources, 1)
|
|
sources.set_height(spacing * (n_sources - 1))
|
|
sources.move_to(film_dot)
|
|
out_wave = LightWaveSlice(sources)
|
|
out_wave.set_color(BLUE_A)
|
|
out_wave.set_shape(20, 20)
|
|
out_wave.move_to(ORIGIN, RIGHT)
|
|
out_wave.set_wave_number(wave_number)
|
|
out_wave.set_frequency(1.0)
|
|
out_wave.set_decay_factor(0)
|
|
out_wave.set_max_amp(10)
|
|
|
|
self.play(
|
|
FadeOut(out_beams),
|
|
GrowFromPoint(out_wave, film_dot.get_center()),
|
|
frame.animate.reorient(0, 0, 0, (-0.1, 1.85, 0.0), 11.08).set_anim_args(run_time=4),
|
|
)
|
|
self.wait(4)
|
|
|
|
def get_radiating_lines(self, point_cloud, obj_dot, length=20, stroke_color=YELLOW, stroke_width=1):
|
|
lines = VGroup()
|
|
for point in point_cloud.get_points():
|
|
line = Line(obj_dot.get_center(), point)
|
|
line.set_length(length)
|
|
line.shift(point - line.get_start())
|
|
line.set_stroke(stroke_color, stroke_width)
|
|
lines.add(line)
|
|
return lines
|
|
|
|
def get_ghost_lines(self, point_cloud, obj_dot, dash_length=0.15, stroke_color=WHITE, stroke_width=1):
|
|
lines = VGroup(
|
|
DashedLine(point, obj_dot.get_center(), dash_length=dash_length)
|
|
for point in point_cloud.get_points()
|
|
)
|
|
lines.set_stroke(stroke_color, stroke_width)
|
|
return lines
|
|
|
|
def get_3d_ref_wave(self, plate, n_planes=50, spacing=1.0, speed=1.0, opacity=0.25):
|
|
planes = Square3D().replicate(n_planes)
|
|
planes.rotate(PI / 2, UP)
|
|
planes.replace(plate, stretch=True)
|
|
planes.arrange(RIGHT, buff=spacing)
|
|
planes.move_to(RIGHT, LEFT)
|
|
for n, plane in enumerate(planes):
|
|
plane.set_color([BLUE, RED][n % 2])
|
|
plane.set_opacity(opacity)
|
|
|
|
def update_planes(planes, dt):
|
|
for plane in planes:
|
|
plane.shift(dt * LEFT * speed)
|
|
x = plane.get_x()
|
|
if x < 1:
|
|
plane.set_opacity(opacity * x)
|
|
if x < 0.1:
|
|
plane.next_to(planes, RIGHT, buff=spacing)
|
|
plane.set_opacity(opacity)
|
|
planes.sort(lambda p: -p[0])
|
|
return planes
|
|
|
|
planes.add_updater(update_planes)
|
|
|
|
return planes
|
|
|
|
def get_triple_beam(self, film_point, obj_point, **kwargs):
|
|
theta = PI - angle_of_vector(film_point - obj_point)
|
|
beams = Group(
|
|
self.get_beam(angle=angle, **kwargs)
|
|
for angle in [-theta, 0, theta]
|
|
)
|
|
beams.shift(film_point)
|
|
beams.deactivate_depth_test()
|
|
return beams
|
|
|
|
def get_beam(self, height=0.1, width=15, n_sources=8, source_height=0.15, wave_number=20, frequency=2.3, color=BLUE_A, opacity=0.75, angle=0):
|
|
mini_sources = DotCloud().to_grid(n_sources, 1)
|
|
mini_sources.set_height(source_height)
|
|
mini_sources.set_radius(0).set_opacity(0)
|
|
mini_sources.move_to(0.75 * RIGHT)
|
|
wave = LightWaveSlice(
|
|
mini_sources,
|
|
wave_number=wave_number,
|
|
frequency=frequency,
|
|
color=color,
|
|
opacity=opacity,
|
|
decay_factor=0.25,
|
|
max_amp=0.4 * n_sources,
|
|
)
|
|
wave.set_shape(width, height)
|
|
wave.move_to(ORIGIN, RIGHT)
|
|
beam = Group(mini_sources, wave)
|
|
beam.rotate(angle, about_point=ORIGIN)
|
|
return beam
|
|
|
|
|
|
class SuperpositionOfPoints(InteractiveScene):
|
|
def construct(self):
|
|
# Set up pi creature dot cloud
|
|
frame = self.frame
|
|
self.set_floor_plane("xz")
|
|
|
|
output_dir = Path(self.file_writer.output_directory)
|
|
data_file = output_dir.parent.joinpath("data", "PiCreaturePointCloud.csv")
|
|
all_points = np.loadtxt(data_file, delimiter=',', skiprows=1)
|
|
all_points = all_points[:int(0.8 * len(all_points))] # Limit to first 400k
|
|
dot_cloud = DotCloud(all_points)
|
|
dot_cloud.set_height(4).center()
|
|
dot_cloud.rotate(50 * DEGREES, DOWN)
|
|
points = dot_cloud.get_points().copy()
|
|
max_z_index = np.argmax(points[:, 2])
|
|
min_z_index = np.argmin(points[:, 2])
|
|
all_points = np.array([points[max_z_index], points[min_z_index], *points])
|
|
|
|
dot_cloud.set_points(all_points[:100_000])
|
|
dot_cloud.set_radius(0.02)
|
|
|
|
# Add axes, points and plate
|
|
plate_center = 5 * IN
|
|
axes = ThreeDAxes(x_range=(-6, 6), y_range=(-4, 4), z_range=(-4, 8))
|
|
axes.shift(plate_center - axes.get_origin())
|
|
|
|
dist_point = 1000 * OUT
|
|
dot_cloud.set_points(np.array([2 * LEFT, 2 * LEFT, dist_point]))
|
|
dot_cloud.set_color(BLUE_B)
|
|
dot_cloud.set_radius(0.5)
|
|
dot_cloud.set_glow_factor(2)
|
|
|
|
plate = LightIntensity(dot_cloud)
|
|
plate.set_color(WHITE)
|
|
plate.set_shape(16, 9)
|
|
plate.set_height(6)
|
|
plate.move_to(plate_center)
|
|
plate.set_wave_number(16)
|
|
plate.set_max_amp(4)
|
|
plate.set_decay_factor(0)
|
|
|
|
frame.reorient(-66, -21, 0, (-0.95, 0.41, -1.11), 11.73)
|
|
frame.clear_updaters()
|
|
frame.add_ambient_rotation(1 * DEGREES)
|
|
self.add(axes)
|
|
self.add(plate)
|
|
self.add(dot_cloud)
|
|
|
|
# Separate out pair of points
|
|
point_sets = [
|
|
(2 * LEFT, RIGHT + OUT),
|
|
(2 * LEFT + IN, RIGHT + OUT),
|
|
(2 * LEFT + IN, 3 * RIGHT + 2 * IN),
|
|
(LEFT + 2 * OUT, RIGHT + OUT),
|
|
]
|
|
|
|
for point_set in point_sets:
|
|
self.play(
|
|
dot_cloud.animate.set_points([*point_set, dist_point]),
|
|
run_time=3
|
|
)
|
|
self.wait(2)
|
|
|
|
# Zoom in on the plate
|
|
frame.clear_updaters()
|
|
self.play(
|
|
frame.animate.reorient(-18, -11, 0, (-1.52, 1.18, -0.67), 0.92),
|
|
run_time=6,
|
|
)
|
|
self.wait()
|
|
|
|
dot_cloud.set_points([point_sets[-1][0], dist_point])
|
|
plate.set_max_amp(3)
|
|
self.wait(2)
|
|
dot_cloud.set_points([point_sets[-1][1], dist_point])
|
|
self.wait(2)
|
|
dot_cloud.set_points([*point_sets[-1], dist_point])
|
|
plate.set_max_amp(4)
|
|
self.play(
|
|
frame.animate.reorient(61, -7, 0, (0.61, -0.11, -2.44), 8.66),
|
|
run_time=5
|
|
)
|
|
|
|
# Add on up to 32 points
|
|
self.play(
|
|
dot_cloud.animate.set_points([*all_points[:2], dist_point]).set_radius(0.2),
|
|
run_time=8
|
|
)
|
|
frame.clear_updaters()
|
|
frame.add_ambient_rotation(0.5 * DEGREES)
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
dot_cloud,
|
|
lambda m, a: m.set_points(
|
|
[*all_points[:int(2 + a * 29)], dist_point]
|
|
)
|
|
),
|
|
UpdateFromFunc(plate, lambda m: m.set_max_amp(2 * np.sqrt(dot_cloud.get_num_points()))),
|
|
run_time=10
|
|
)
|
|
self.wait(2)
|
|
|
|
# Describe as a combination of zone plates
|
|
zone_plates = Group()
|
|
for point in all_points[:30]:
|
|
zone_plate = LightIntensity(DotCloud([point, dist_point]))
|
|
zone_plate.set_uniforms(dict(plate.uniforms))
|
|
zone_plate.match_points(plate)
|
|
zone_plate.set_max_amp(10)
|
|
zone_plate.set_opacity(0.25)
|
|
zone_plates.add(zone_plate)
|
|
|
|
zone_plates.deactivate_depth_test()
|
|
self.remove(plate)
|
|
self.add(zone_plates)
|
|
|
|
for n, zone_plate, point in zip(it.count(1), zone_plates, all_points[:30]):
|
|
zone_plate.shift(1e-2 * IN)
|
|
zone_plate.save_state()
|
|
zone_plate.scale(0).move_to(point).set_max_amp(2).set_opacity(1)
|
|
|
|
self.play(
|
|
UpdateFromAlphaFunc(plate, lambda m, a: m.set_opacity(1 - there_and_back_with_pause(a, 0.6))),
|
|
LaggedStartMap(Restore, zone_plates, lag_ratio=0.05),
|
|
frame.animate.reorient(66, -18, 0, (1.44, 0.59, -6.18), 16.05),
|
|
run_time=5
|
|
)
|
|
self.play(FadeOut(zone_plates))
|
|
|
|
# Move around points
|
|
frame.clear_updaters()
|
|
frame.add_ambient_rotation(-2 * DEGREES)
|
|
|
|
dot_cloud.save_state()
|
|
self.play(dot_cloud.animate.shift(2 * IN), run_time=3)
|
|
self.play(Rotate(dot_cloud, PI / 2 , axis=UP, about_point=ORIGIN), run_time=3)
|
|
self.play(Restore(dot_cloud), run_time=3)
|
|
self.wait(3)
|
|
|
|
# Show reference wave through it
|
|
rect = Rectangle().replace(plate, stretch=True)
|
|
rect.insert_n_curves(20)
|
|
globals().update(locals())
|
|
beam = VGroup(
|
|
Line(25 * OUT, rect.pfp(a))
|
|
for a in np.linspace(0, 1, 500)
|
|
)
|
|
beam.set_stroke(GREEN_SCREEN, (1, 0), 0.5)
|
|
beam.shuffle()
|
|
|
|
self.play(
|
|
ShowCreation(beam, lag_ratio=1 / len(beam)),
|
|
FadeOut(dot_cloud),
|
|
frame.animate.reorient(83, -27, 0, (-0.63, -0.01, -0.79), 14.36),
|
|
run_time=2
|
|
)
|
|
frame.clear_updaters()
|
|
dot_cloud.set_color(GREEN_SCREEN)
|
|
|
|
# Test
|
|
frame.reorient(-26, -9, 0, (0.15, -0.46, -0.42), 15.13)
|
|
self.play(frame.animate.reorient(0, 0, 0, (0.53, 0.16, -0.0), 0.22), run_time=8)
|
|
self.play(frame.animate.reorient(3, 0, 0, (0.14, -0.02, 0.03), 0.22), run_time=8)
|
|
|
|
# Build it up again from the other side
|
|
self.play(
|
|
plate.animate.set_opacity(0.2).set_anim_args(time_span=(1.6, 1.7)),
|
|
frame.animate.reorient(162, -3, 0, (-0.89, 0.06, 0.03), 12.77),
|
|
run_time=4,
|
|
)
|
|
dot_cloud.set_points([all_points[0], dist_point])
|
|
plate.set_max_amp(2 * np.sqrt(dot_cloud.get_num_points()))
|
|
self.add(dot_cloud)
|
|
self.wait()
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
dot_cloud,
|
|
lambda m, a: m.set_points(
|
|
[*all_points[:int(1 + a * 29)], dist_point]
|
|
)
|
|
),
|
|
UpdateFromFunc(plate, lambda m: m.set_max_amp(2 * np.sqrt(dot_cloud.get_num_points()))),
|
|
frame.animate.reorient(207, -8, 0, (-0.89, 0.06, 0.03), 12.77),
|
|
run_time=12
|
|
)
|
|
self.wait(2)
|
|
|
|
# Close up on cloud
|
|
self.play(
|
|
FadeOut(beam),
|
|
FadeOut(dot_cloud),
|
|
frame.animate.reorient(185, -39, 0, (-0.89, 0.06, 0.03), 12.77).set_anim_args(run_time=3),
|
|
)
|
|
dot_cloud.set_color(BLUE).set_glow_factor(1).set_radius(0.1)
|
|
self.play(
|
|
FadeOut(plate),
|
|
FadeIn(dot_cloud),
|
|
frame.animate.reorient(115, -16, 0, (0.33, 0.28, -0.52), 4.38),
|
|
run_time=3
|
|
)
|
|
|
|
# Denser cloud
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
dot_cloud,
|
|
lambda m, a: m.set_points(
|
|
[*all_points[:int(interpolate(31, 500, a))], dist_point]
|
|
).set_glow_factor(interpolate(1, 0, a**0.25)).set_radius(interpolate(0.1, 0.02, a**0.25)).set_opacity(interpolate(1, 0.5, a**0.25)),
|
|
),
|
|
run_time=4
|
|
)
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
dot_cloud,
|
|
lambda m, a: m.set_points(
|
|
[*all_points[:int(interpolate(500, len(all_points), a**3))]]
|
|
).set_radius(interpolate(0.02, 0.01, a)).set_opacity(interpolate(0.5, 0.2, a)),
|
|
),
|
|
run_time=5
|
|
)
|
|
|
|
# Add better updating film
|
|
sheet_dots = self.create_dot_sheet(plate.get_width(), plate.get_height(), radius=0.025, z=plate.get_z())
|
|
self.color_sheet_by_exposure(sheet_dots, dot_cloud.get_points()[:1000], wave_number=32)
|
|
self.add(sheet_dots)
|
|
|
|
self.play(
|
|
frame.animate.reorient(-16, -45, 0, (-0.97, 1.52, -1.18), 8.67),
|
|
run_time=2,
|
|
)
|
|
frame.clear_updaters()
|
|
frame.add_ambient_rotation(3 * DEGREES)
|
|
|
|
# Move dot cloud around
|
|
self.play(
|
|
dot_cloud.animate.shift(2 * IN),
|
|
UpdateFromFunc(sheet_dots, lambda m: self.color_sheet_by_exposure(m, dot_cloud.get_points()[:1000], wave_number=32)),
|
|
run_time=3,
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
Rotate(dot_cloud, 120 * DEGREES, axis=UP),
|
|
UpdateFromFunc(sheet_dots, lambda m: self.color_sheet_by_exposure(m, dot_cloud.get_points()[:1000], wave_number=32)),
|
|
run_time=3,
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
dot_cloud.animate.shift(3 * OUT),
|
|
UpdateFromFunc(sheet_dots, lambda m: self.color_sheet_by_exposure(m, dot_cloud.get_points()[:1000], wave_number=32)),
|
|
run_time=3,
|
|
)
|
|
self.wait(3)
|
|
|
|
# Transform point into film
|
|
frame.clear_updaters()
|
|
frame.reorient(111, -13, 0, (-0.54, 0.04, -1.71), 5.72)
|
|
pre_dots = dot_cloud.copy()
|
|
pre_dots.set_points(dot_cloud.get_points()[:len(sheet_dots.get_points())])
|
|
|
|
self.play(
|
|
TransformFromCopy(pre_dots, sheet_dots, time_span=(2, 8)),
|
|
frame.animate.reorient(17, -19, 0, (0.82, 0.57, -3.07), 7.99),
|
|
run_time=12
|
|
)
|
|
self.play(
|
|
dot_cloud.animate.shift(2 * IN),
|
|
UpdateFromFunc(sheet_dots, lambda m: self.color_sheet_by_exposure(m, dot_cloud.get_points()[:1000], wave_number=32)),
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
|
|
def color_sheet_by_exposure(self, sheet_dots, point_sources, wave_number=16, opacity=0.5):
|
|
centers = sheet_dots.get_points()
|
|
diffs = centers[:, np.newaxis, :] - point_sources[np.newaxis, :, :]
|
|
distances = np.linalg.norm(diffs, axis=2)
|
|
amplitudes = np.exp(distances * TAU * 1j * wave_number).sum(1)
|
|
mags = abs(amplitudes)
|
|
max_amp = 2 * np.sqrt(len(point_sources))
|
|
opacities = opacity * np.clip(mags / max_amp, 0, 1)
|
|
sheet_dots.set_opacity(opacities)
|
|
return sheet_dots
|
|
|
|
def create_dot_sheet(self, width=4, height=4, radius=0.05, z=0, make_3d=False):
|
|
# Add dots
|
|
dots = DotCloud()
|
|
dots.set_color(WHITE)
|
|
dots.to_grid(int(height / radius), int(width / radius))
|
|
dots.set_shape(width, height)
|
|
dots.set_radius(radius)
|
|
dots.set_z(z)
|
|
|
|
if make_3d:
|
|
dots.make_3d()
|
|
|
|
return dots
|
|
|
|
|
|
class ComplexWavesBase(InteractiveScene):
|
|
def construct(self):
|
|
# Transition from TwoInterferingWaves to just show the object wave
|
|
# Maybe it makes more sense to do that from TwoInterferingWaves itself?
|
|
pass
|
|
|
|
|
|
class ComplexWaves(InteractiveScene):
|
|
def construct(self):
|
|
# Add Amplitude(R + O)^2
|
|
amp_expr = Tex(R"\text{Amplitude}(R + O)^2", font_size=60)
|
|
amp_expr.to_edge(UP)
|
|
RO = amp_expr[R"R + O"][0]
|
|
RO.save_state()
|
|
RO.set_x(0)
|
|
|
|
self.play(FadeIn(RO, UP))
|
|
self.wait()
|
|
self.play(
|
|
Write(amp_expr[R"\text{Amplitude}("][0]),
|
|
Write(amp_expr[R")"][0], time_span=(1.5, 2)),
|
|
Restore(RO, time_span=(0.5, 1.5)),
|
|
run_time=2
|
|
)
|
|
self.wait()
|
|
self.play(FadeIn(amp_expr[R"^2"], 0.25 * UP, scale=0.8))
|
|
self.wait()
|
|
|
|
# Expand as functions of (x, y, z, t)
|
|
amp_expr.save_state()
|
|
|
|
O_func = Tex("O(x, y, z, t)", font_size=60)
|
|
O_func.move_to(UP + LEFT)
|
|
|
|
xyz_rect = SurroundingRectangle(O_func["x, y, z"], buff=0.05)
|
|
xyz_rect.set_stroke(YELLOW)
|
|
xyz_rect.stretch(1.3, 1, about_edge=DOWN)
|
|
xyz_rect.round_corners()
|
|
xyz_arrow = Vector(2.2 * UP, thickness=5).next_to(xyz_rect, DOWN)
|
|
xyz_arrow.set_backstroke(BLACK, 4)
|
|
space_words = Text("Point\nin space", font_size=36)
|
|
space_words.next_to(xyz_rect, UP)
|
|
|
|
time_rect = SurroundingRectangle(O_func["t"], buff=0.05)
|
|
time_rect.match_height(xyz_rect, stretch=True, about_edge=DOWN)
|
|
time_rect.align_to(xyz_rect, DOWN)
|
|
time_rect.round_corners()
|
|
time_rect.set_stroke(TEAL)
|
|
time_word = Text("Time", font_size=36) # Make a clock instead?
|
|
time_word.next_to(time_rect, UP)
|
|
|
|
self.play(
|
|
amp_expr.animate.scale(0.5).to_corner(UL).set_opacity(0.5),
|
|
TransformFromCopy(amp_expr["O"][0], O_func["O"][0]),
|
|
)
|
|
self.play(Write(O_func[1:], run_time=1, stroke_color=WHITE))
|
|
O_func.set_backstroke(BLACK, 5)
|
|
self.play(
|
|
ShowCreation(xyz_rect),
|
|
GrowArrow(xyz_arrow),
|
|
FadeIn(space_words, lag_ratio=0.1),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeTransformPieces(space_words, time_word),
|
|
ShowCreation(time_rect),
|
|
)
|
|
self.wait()
|
|
|
|
# Show O(x, y, z, t) outputting to a real line
|
|
frequency = 0.25
|
|
amplitude = 1.5
|
|
out_arrow = Vector(RIGHT, thickness=4)
|
|
out_arrow.next_to(O_func, RIGHT)
|
|
|
|
real_line = NumberLine((-2, 2, 0.25), width=4, tick_size=0.025, big_tick_spacing=1.0, longer_tick_multiple=3)
|
|
real_line.next_to(out_arrow, RIGHT)
|
|
plane = ComplexPlane(
|
|
(-2, 2), (-2, 2),
|
|
width=4,
|
|
background_line_style=dict(
|
|
stroke_color=GREY_C,
|
|
stroke_width=1,
|
|
),
|
|
faded_line_style=dict(
|
|
stroke_color=GREY_D,
|
|
stroke_width=0.5,
|
|
stroke_opacity=1,
|
|
)
|
|
)
|
|
plane.move_to(real_line)
|
|
|
|
real_line.add_numbers(list(range(-2, 3)), font_size=16)
|
|
plane.add_coordinate_labels(font_size=16)
|
|
plane.set_stroke(behind=True)
|
|
|
|
time_tracker = ValueTracker()
|
|
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
|
|
|
|
def get_z():
|
|
return amplitude * np.exp(complex(0, TAU * frequency * time_tracker.get_value()))
|
|
|
|
def get_z_point():
|
|
return plane.n2p(get_z())
|
|
|
|
real_indicator = Group(GlowDot(radius=0.3), TrueDot().make_3d())
|
|
|
|
def update_real_indicator(indicator):
|
|
x = get_z().real
|
|
indicator.move_to(plane.n2p(x))
|
|
if x > 0:
|
|
indicator.set_color(interpolate_color(GREY_D, BLUE, x / amplitude))
|
|
else:
|
|
indicator.set_color(interpolate_color(GREY_D, RED, -x / amplitude))
|
|
|
|
real_indicator.add_updater(update_real_indicator)
|
|
|
|
self.add(time_tracker)
|
|
self.play(
|
|
GrowArrow(out_arrow),
|
|
FadeIn(real_line),
|
|
xyz_rect.animate.set_stroke(width=1),
|
|
time_rect.animate.set_stroke(width=1),
|
|
FadeOut(time_word),
|
|
FadeIn(real_indicator)
|
|
)
|
|
self.wait(12)
|
|
|
|
# Extend to complex plane
|
|
complex_label = Text("Complex Plane")
|
|
complex_label.next_to(plane, UP)
|
|
complex_dot = real_indicator.copy()
|
|
complex_dot.clear_updaters()
|
|
complex_dot.set_color(YELLOW)
|
|
complex_dot.f_always.move_to(get_z_point)
|
|
|
|
complex_arrow = Vector(RIGHT)
|
|
complex_arrow.set_color(YELLOW)
|
|
complex_arrow.f_always.put_start_and_end_on(plane.get_origin, get_z_point)
|
|
|
|
v_line = Line(UP, DOWN)
|
|
v_line.set_stroke(GREY, 1)
|
|
v_line.f_always.put_start_and_end_on(get_z_point, real_indicator.get_center)
|
|
|
|
self.add(plane, real_indicator)
|
|
self.play(
|
|
FadeIn(plane),
|
|
FadeOut(real_line),
|
|
FadeIn(complex_arrow),
|
|
FadeIn(v_line),
|
|
)
|
|
self.play(Write(complex_label))
|
|
self.wait(12)
|
|
|
|
# Get into a good position
|
|
time_tracker.resume_updating()
|
|
self.wait_until(lambda: 0.4 < time_tracker.get_value() % 4 < 0.5)
|
|
time_tracker.suspend_updating()
|
|
|
|
# Mention amplitude and phase
|
|
angle = complex_arrow.get_angle()
|
|
rot_arrow = complex_arrow.copy()
|
|
rot_arrow.clear_updaters()
|
|
rot_arrow.rotate(-angle, about_point=rot_arrow.get_start())
|
|
rot_arrow.set_opacity(0)
|
|
brace = Brace(rot_arrow, UP, buff=0)
|
|
amp_label = brace.get_text("Amplitude", font_size=30)
|
|
amp_label.set_backstroke(BLACK, 5)
|
|
VGroup(brace, amp_label).rotate(angle, about_point=complex_arrow.get_start())
|
|
|
|
arc = Arc(0, angle, radius=0.5, arc_center=plane.get_origin())
|
|
phase_label = Text("Phase", font_size=30)
|
|
phase_label.next_to(arc, RIGHT, SMALL_BUFF)
|
|
phase_label.shift(SMALL_BUFF * UR)
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(amp_label),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(arc),
|
|
TransformFromCopy(rot_arrow, complex_arrow, path_arc=angle),
|
|
Write(phase_label)
|
|
)
|
|
self.wait()
|
|
|
|
# Re-emphasize the real component
|
|
self.play(FadeOut(VGroup(brace, amp_label, arc, phase_label)))
|
|
time_tracker.resume_updating()
|
|
|
|
plane.save_state()
|
|
self.play(
|
|
plane.animate.fade(0.75),
|
|
FadeIn(real_line),
|
|
complex_arrow.animate.set_fill(opacity=0.25)
|
|
)
|
|
self.wait(8)
|
|
self.play(
|
|
Restore(plane),
|
|
FadeOut(real_line),
|
|
complex_arrow.animate.set_fill(opacity=1.0)
|
|
)
|
|
self.wait(8)
|
|
self.play(
|
|
FadeOut(xyz_rect),
|
|
FadeOut(time_rect),
|
|
FadeOut(xyz_arrow),
|
|
FadeOut(out_arrow),
|
|
FadeOut(real_indicator),
|
|
FadeOut(v_line),
|
|
)
|
|
|
|
# Package back into R + O expression
|
|
time_tracker.suspend_updating()
|
|
|
|
self.remove(complex_label)
|
|
plane.add(complex_label)
|
|
self.add(plane, complex_arrow)
|
|
self.play(
|
|
Transform(O_func, amp_expr.saved_state[-3], remover=True),
|
|
Restore(amp_expr),
|
|
plane.animate.move_to(DOWN),
|
|
run_time=2
|
|
)
|
|
|
|
# Add R arrow
|
|
O_arrow = complex_arrow
|
|
O_arrow.clear_updaters()
|
|
R_arrow = Vector().set_color(TEAL)
|
|
comb_arrow = Vector().set_color(GREY_B)
|
|
|
|
R_phase_tracker = ValueTracker(30 * DEGREES)
|
|
O_phase_tracker = ValueTracker(complex_arrow.get_angle())
|
|
R_amp = math.sqrt(2)
|
|
O_amp = 1.5
|
|
|
|
def get_R():
|
|
return R_amp * np.exp(complex(0, R_phase_tracker.get_value()))
|
|
|
|
def get_O():
|
|
return O_amp * np.exp(complex(0, O_phase_tracker.get_value()))
|
|
|
|
R_arrow.put_start_and_end_on(plane.get_origin(), plane.n2p(get_R()))
|
|
comb_arrow.put_start_and_end_on(plane.get_origin(), plane.n2p(get_R() + get_O()))
|
|
|
|
R_label = self.get_arrow_label(R_arrow, "R")
|
|
O_label = self.get_arrow_label(O_arrow, "O")
|
|
comb_label = self.get_arrow_label(comb_arrow, "R + O", buff=-0.5)
|
|
|
|
self.play(
|
|
GrowArrow(R_arrow),
|
|
O_arrow.animate.shift(R_arrow.get_vector()),
|
|
FadeIn(R_label),
|
|
FadeIn(O_label),
|
|
)
|
|
self.play(
|
|
FadeIn(comb_arrow),
|
|
FadeIn(comb_label),
|
|
)
|
|
self.wait()
|
|
|
|
R_arrow.add_updater(lambda m: m.put_start_and_end_on(
|
|
plane.get_origin(), plane.n2p(get_R())
|
|
))
|
|
O_arrow.add_updater(lambda m: m.put_start_and_end_on(
|
|
plane.n2p(get_R()), plane.n2p(get_R() + get_O()),
|
|
))
|
|
comb_arrow.add_updater(lambda m: m.put_start_and_end_on(
|
|
plane.get_origin(), plane.n2p(get_R() + get_O()),
|
|
))
|
|
|
|
# Write |R + O|^2
|
|
lhs = Text("Film opacity = ")
|
|
lhs.move_to(amp_expr, LEFT)
|
|
lhs.set_color(GREY_B)
|
|
new_amp_expr = Tex(R"c|R + O|^2")
|
|
new_amp_expr.next_to(lhs, RIGHT)
|
|
|
|
self.play(
|
|
ReplacementTransform(amp_expr["(R + O)^2"][0], new_amp_expr),
|
|
FadeOut(amp_expr["Amplitude"][0]),
|
|
)
|
|
self.play(FadeIn(lhs, lag_ratio=0.1))
|
|
self.wait()
|
|
|
|
# Change R and O values
|
|
self.play(
|
|
R_phase_tracker.animate.set_value(-45 * DEGREES),
|
|
O_phase_tracker.animate.set_value(-45 * DEGREES), run_time=2
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
O_phase_tracker.animate.set_value(-125 * DEGREES),
|
|
R_phase_tracker.animate.set_value(45 * DEGREES),
|
|
run_time=3
|
|
)
|
|
self.wait()
|
|
|
|
def get_arrow_label(self, arrow, symbol, font_size=24, buff=0.25):
|
|
result = Tex(symbol, font_size=font_size)
|
|
result.match_color(arrow)
|
|
result.add_updater(lambda m: m.move_to(arrow.get_center() + buff * normalize(rotate_vector(arrow.get_vector(), PI / 2))))
|
|
return result
|
|
|
|
|
|
class StateOnA2DScreen(InteractiveScene):
|
|
def construct(self):
|
|
# Add screen
|
|
frame = self.frame
|
|
self.set_floor_plane("xz")
|
|
|
|
screen = ScreenRectangle()
|
|
screen.set_height(6)
|
|
screen.set_stroke(WHITE, 1)
|
|
|
|
source_points = DotCloud(np.random.random((10, 3)))
|
|
source_points.set_width(8)
|
|
source_points.move_to(screen, OUT)
|
|
wave2d = LightWaveSlice(source_points)
|
|
wave2d.replace(screen, stretch=True)
|
|
wave2d.set_wave_number(4)
|
|
wave2d.set_frequency(1)
|
|
wave2d.set_max_amp(4)
|
|
wave2d.set_decay_factor(0)
|
|
|
|
axes = ThreeDAxes((-5, 5), (-5, 5), (-5, 5))
|
|
|
|
self.add(axes)
|
|
self.add(screen)
|
|
self.add(wave2d)
|
|
self.wait(4)
|
|
|
|
# Zoom out to 3d waves
|
|
wave3d = Group(
|
|
# wave2d.copy().rotate(PI / 2, RIGHT).stretch(10, 2).move_to(ORIGIN, IN)
|
|
)
|
|
n_slices = 3
|
|
for x in np.linspace(-5, 5, n_slices):
|
|
wave_slice = wave2d.copy()
|
|
wave_slice.scale(20)
|
|
wave_slice.rotate(PI / 2, UP)
|
|
wave_slice.move_to(ORIGIN, IN)
|
|
wave_slice.set_x(x)
|
|
wave_slice.set_opacity(1.0 / n_slices)
|
|
wave3d.add(wave_slice)
|
|
|
|
for wave in wave3d:
|
|
wave.set_opacity(1)
|
|
wave.set_max_amp(10)
|
|
|
|
# wave3d[n_slices // 2].set_opacity(0.75)
|
|
# wave3d.set_opacity(0.1)
|
|
# wave3d.save_state()
|
|
# wave3d.stretch(0, dim=2, about_point=ORIGIN)
|
|
|
|
self.play(
|
|
frame.animate.reorient(101, -1, 0, (-0.53, 0.13, 7.82), 13.40),
|
|
FadeIn(wave3d, time_span=(3, 8)),
|
|
run_time=8,
|
|
)
|
|
|
|
# Linger and collapse
|
|
self.wait(4)
|
|
self.play(
|
|
LaggedStart(*(
|
|
wave.animate.match_points(wave2d).set_opacity(1).shift(1e-2 * IN)
|
|
for wave in wave3d
|
|
), lag_ratio=0),
|
|
frame.animate.reorient(25, -6, 0, (0.18, 0.29, 0.15), 9.15).set_anim_args(time_span=(1, 4)),
|
|
run_time=4
|
|
)
|
|
self.wait(8)
|
|
|
|
|
|
## Old ##
|
|
|
|
class PointSourceDiffractionPattern(InteractiveScene):
|
|
# default_frame_orientation = (-35, -10)
|
|
include_axes = True
|
|
axes_config = dict(
|
|
x_range=(-6, 6),
|
|
y_range=(-6, 6),
|
|
z_range=(-6, 6),
|
|
)
|
|
light_frequency = 2.0
|
|
wave_length = 1.0
|
|
use_hue = False
|
|
max_mag = 3.0
|
|
|
|
def setup(self):
|
|
super().setup()
|
|
|
|
self.set_floor_plane("xz")
|
|
self.frame.reorient(-35, -10)
|
|
|
|
if self.include_axes:
|
|
axes = self.axes = ThreeDAxes(**self.axes_config)
|
|
axes.set_stroke(opacity=0.5)
|
|
self.add(axes)
|
|
|
|
# Set up light sources
|
|
points = self.point_sources = DotCloud(self.get_point_source_locations())
|
|
points.set_glow_factor(2)
|
|
points.set_radius(0.2)
|
|
points.set_color(WHITE)
|
|
self.add(points)
|
|
|
|
# Add frequency trackerg
|
|
self.frequency_tracker = ValueTracker(self.light_frequency)
|
|
self.wave_length_tracker = ValueTracker(self.wave_length)
|
|
self.light_time_tracker = ValueTracker(0)
|
|
self.max_mag_tracker = ValueTracker(self.max_mag)
|
|
|
|
def get_point_source_locations(self):
|
|
radius = 2.0
|
|
n_sources = 5
|
|
ring = Circle(radius=radius)
|
|
ring.set_stroke(WHITE, 3)
|
|
return np.array([ring.pfp(a) for a in np.arange(0, 1, 1 / n_sources)])
|
|
|
|
def get_light_time(self):
|
|
return self.light_time_tracker.get_value()
|
|
|
|
def get_frequency(self):
|
|
return self.frequency_tracker.get_value()
|
|
|
|
def color_dot_cloud_by_diffraction(self, dot_cloud):
|
|
frequency = self.get_frequency()
|
|
point_sources = self.point_sources.get_points()
|
|
max_mag = self.max_mag_tracker.get_value()
|
|
|
|
centers = dot_cloud.get_points()
|
|
diffs = centers[:, np.newaxis, :] - point_sources[np.newaxis, :, :]
|
|
distances = np.linalg.norm(diffs, axis=2)
|
|
amplitudes = np.exp(distances * TAU * 1j * frequency).sum(1)
|
|
mags = abs(amplitudes)
|
|
opacities = 0.5 * np.clip(mags / max_mag, 0, 1)
|
|
|
|
n = len(centers)
|
|
rgbas = dot_cloud.data["rgba"]
|
|
rgbas[:, 3] = opacities
|
|
|
|
if self.use_hue:
|
|
hues = (np.log(amplitudes).imag / TAU) % 1
|
|
hsl = 0.5 * np.ones((n, 3))
|
|
hsl[:, 0] = hues
|
|
rgbas[:, :3] = hsl_to_rgb(hsl)
|
|
|
|
dot_cloud.set_rgba_array(rgbas)
|
|
return dot_cloud
|
|
|
|
def create_dot_sheet(self, width=4, height=4, radius=0.05, z=0, make_3d=False):
|
|
# Add dots
|
|
dots = DotCloud()
|
|
dots.set_color(WHITE)
|
|
dots.to_grid(int(height / radius / 2), int(width / radius / 2))
|
|
dots.set_shape(width, height)
|
|
dots.set_radius(radius)
|
|
dots.add_updater(self.color_dot_cloud_by_diffraction)
|
|
dots.suspend_updating = lambda: None # Never suspend!
|
|
dots.set_z(z)
|
|
|
|
if make_3d:
|
|
dots.make_3d()
|
|
|
|
return dots
|
|
|
|
# TODO, have a picture-in-picture graph showing the sine waves for a given source
|
|
# TODO, have a picture in picture phasor
|