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