More diffraction animations

This commit is contained in:
Grant Sanderson 2024-09-21 12:14:19 -04:00
parent 1fd090ac37
commit 2db4ee4e0c
5 changed files with 2671 additions and 301 deletions

View file

@ -577,170 +577,6 @@ class AccelerationVector(Vector):
self.put_start_and_end_on(center, center + a_vect)
class VectorField(VMobject):
def __init__(
self,
func,
stroke_color=BLUE,
center=ORIGIN,
x_density=2.0,
y_density=2.0,
z_density=2.0,
width=14,
height=8,
depth=0,
stroke_width: float = 2,
tip_width_ratio: float = 4,
tip_len_to_width: float = 0.01,
max_vect_len: float | None = None,
min_drawn_norm: float = 1e-2,
flat_stroke=False,
norm_to_opacity_func=None,
norm_to_rgb_func=None,
**kwargs
):
self.func = func
self.stroke_width = stroke_width
self.tip_width_ratio = tip_width_ratio
self.tip_len_to_width = tip_len_to_width
self.min_drawn_norm = min_drawn_norm
self.norm_to_opacity_func = norm_to_opacity_func
self.norm_to_rgb_func = norm_to_rgb_func
if max_vect_len is not None:
self.max_vect_len = max_vect_len
else:
densities = np.array([x_density, y_density, z_density])
dims = np.array([width, height, depth])
self.max_vect_len = 1.0 / densities[dims > 0].mean()
self.sample_points = self.get_sample_points(
center, width, height, depth,
x_density, y_density, z_density
)
self.init_base_stroke_width_array(len(self.sample_points))
super().__init__(
stroke_color=stroke_color,
flat_stroke=flat_stroke,
**kwargs
)
n_samples = len(self.sample_points)
self.set_points(np.zeros((8 * n_samples - 1, 3)))
self.set_stroke(width=stroke_width)
self.update_vectors()
def get_sample_points(
self,
center: np.ndarray,
width: float,
height: float,
depth: float,
x_density: float,
y_density: float,
z_density: float
) -> np.ndarray:
to_corner = np.array([width / 2, height / 2, depth / 2])
spacings = 1.0 / np.array([x_density, y_density, z_density])
to_corner = spacings * (to_corner / spacings).astype(int)
lower_corner = center - to_corner
upper_corner = center + to_corner + spacings
return cartesian_product(*(
np.arange(low, high, space)
for low, high, space in zip(lower_corner, upper_corner, spacings)
))
def init_base_stroke_width_array(self, n_sample_points):
arr = np.ones(8 * n_sample_points - 1)
arr[4::8] = self.tip_width_ratio
arr[5::8] = self.tip_width_ratio * 0.5
arr[6::8] = 0
arr[7::8] = 0
self.base_stroke_width_array = arr
def set_stroke(self, color=None, width=None, opacity=None, background=None, recurse=True):
super().set_stroke(color, None, opacity, background, recurse)
if width is not None:
self.set_stroke_width(float(width))
return self
def set_stroke_width(self, width: float):
if self.get_num_points() > 0:
self.get_stroke_widths()[:] = width * self.base_stroke_width_array
self.stroke_width = width
return self
def update_vectors(self):
tip_width = self.tip_width_ratio * self.stroke_width
tip_len = self.tip_len_to_width * tip_width
samples = self.sample_points
# Get raw outputs and lengths
outputs = self.func(samples)
norms = np.linalg.norm(outputs, axis=1)[:, np.newaxis]
# How long should the arrows be drawn?
max_len = self.max_vect_len
if max_len < np.inf:
drawn_norms = max_len * np.tanh(norms / max_len)
else:
drawn_norms = norms
# What's the distance from the base of an arrow to
# the base of its head?
dist_to_head_base = np.clip(drawn_norms - tip_len, 0, np.inf)
# Set all points
unit_outputs = np.zeros_like(outputs)
np.true_divide(outputs, norms, out=unit_outputs, where=(norms > self.min_drawn_norm))
points = self.get_points()
points[0::8] = samples
points[2::8] = samples + dist_to_head_base * unit_outputs
points[4::8] = points[2::8]
points[6::8] = samples + drawn_norms * unit_outputs
for i in (1, 3, 5):
points[i::8] = 0.5 * (points[i - 1::8] + points[i + 1::8])
points[7::8] = points[6:-1:8]
# Adjust stroke widths
width_arr = self.stroke_width * self.base_stroke_width_array
width_scalars = np.clip(drawn_norms / tip_len, 0, 1)
width_scalars = np.repeat(width_scalars, 8)[:-1]
self.get_stroke_widths()[:] = width_scalars * width_arr
# Potentially adjust opacity and color
if self.norm_to_opacity_func is not None:
self.get_stroke_opacities()[:] = self.norm_to_opacity_func(
np.repeat(norms, 8)[:-1]
)
if self.norm_to_rgb_func is not None:
self.get_stroke_colors()
self.data['stroke_rgba'][:, :3] = self.norm_to_rgb_func(
np.repeat(norms, 8)[:-1]
)
self.note_changed_data()
return self
class TimeVaryingVectorField(VectorField):
def __init__(
self,
# Takes in an array of points and a float for time
time_func,
**kwargs
):
self.time = 0
super().__init__(func=lambda p: time_func(p, self.time), **kwargs)
self.add_updater(lambda m, dt: m.increment_time(dt))
always(self.update_vectors)
def increment_time(self, dt):
self.time += dt
class ChargeBasedVectorField(VectorField):
default_color = BLUE

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ uniform float wave_number;
uniform float max_amp;
uniform float n_sources;
uniform float time;
uniform float decay_factor;
uniform float show_intensity;
@ -30,21 +31,45 @@ uniform vec3 point_source12;
uniform vec3 point_source13;
uniform vec3 point_source14;
uniform vec3 point_source15;
uniform vec3 point_source16;
uniform vec3 point_source17;
uniform vec3 point_source18;
uniform vec3 point_source19;
uniform vec3 point_source20;
uniform vec3 point_source21;
uniform vec3 point_source22;
uniform vec3 point_source23;
uniform vec3 point_source24;
uniform vec3 point_source25;
uniform vec3 point_source26;
uniform vec3 point_source27;
uniform vec3 point_source28;
uniform vec3 point_source29;
uniform vec3 point_source30;
uniform vec3 point_source31;
in vec3 frag_point;
out vec4 frag_color;
const float TAU = 6.283185307179586;
const float PLANE_WAVE_THRESHOLD = 999.0;
vec2 amp_from_source(vec3 source){
float dist = distance(frag_point, source);
float source_dist = length(source);
bool plane_wave = source_dist >= PLANE_WAVE_THRESHOLD;
float dist = plane_wave ?
source_dist - dot(frag_point, source / source_dist) :
distance(frag_point, source);
float term = TAU * (wave_number * dist - frequency * time);
return vec2(cos(term), sin(term)) / dist;
return vec2(cos(term), sin(term)) * pow(1.0 + dist, -decay_factor);
}
void main() {
if (opacity == 0) discard;
frag_color.rgb = color;
vec3 point_sources[16] = vec3[16](
vec3 point_sources[32] = vec3[32](
point_source0,
point_source1,
point_source2,
@ -60,7 +85,23 @@ void main() {
point_source12,
point_source13,
point_source14,
point_source15
point_source15,
point_source16,
point_source17,
point_source18,
point_source19,
point_source20,
point_source21,
point_source22,
point_source23,
point_source24,
point_source25,
point_source26,
point_source27,
point_source28,
point_source29,
point_source30,
point_source31
);
vec2 amp = vec2(0);
for(int i = 0; i < int(n_sources); i++){
@ -68,5 +109,8 @@ void main() {
}
// Display either the amplitude of the wave, or its value at this point/time
float magnitude = bool(show_intensity) ? length(amp) : amp.x;
frag_color.a = opacity * smoothstep(0, max_amp, magnitude);
// Invert color for negative values
if (magnitude < 0) frag_color.rgb = 1.0 - frag_color.rgb;
frag_color.a = opacity * smoothstep(0, max_amp, abs(magnitude));
}

View file

@ -5,7 +5,7 @@ from manim_imports_ext import *
class ExtractFramesFromFootage(InteractiveScene):
video_file = "/Users/grant/3Blue1Brown Dropbox/3Blue1Brown/videos/2024/holograms/SceneModel/MultiplePOVs.mp4"
video_file = "/Users/grant/3Blue1Brown Dropbox/3Blue1Brown/videos/2024/holograms/SceneModel/MultiplePOVs.2.mp4"
image_dir = "/tmp/"
frequency = 0.25
start_time = 0
@ -57,7 +57,7 @@ class ExtractFramesFromFootage(InteractiveScene):
# Add still
still_image = images[-1].copy()
still_image.move_to(video_box)
still_image.replace(video_box)
self.add(still_image)
self.wait()

View file

@ -0,0 +1,45 @@
from manim_imports_ext import *
class DoubleSlitSupplementaryGraphs(InteractiveScene):
def construct(self):
# Setup all three axes, with labels
# Show constructive interference
# Show destructive interference
...
class DistApproximations(InteractiveScene):
def construct(self):
# Show sqrt(L^2 + x^2) approx L + x/(2L) approx L
pass
class DiffractionEquation(InteractiveScene):
def construct(self):
# Add equation
equation = Tex(R"{d} \cdot \sin(\theta) = \lambda", font_size=60)
equation.set_backstroke(BLACK)
arrow = Vector(DOWN, thickness=4)
arrow.set_color(BLUE)
globals().update(locals())
d, theta, lam = syms = [equation[s][0] for s in [R"{d}", R"\theta", R"\lambda"]]
colors = [BLUE, YELLOW, TEAL]
arrow.next_to(d, UP, LARGE_BUFF)
arrow.set_fill(opacity=0)
self.add(equation)
for sym, color in zip(syms, colors):
self.play(
FlashAround(sym, color=color, time_span=(0.25, 1.25)),
sym.animate.set_fill(color),
arrow.animate.next_to(sym, UP).set_fill(color, 1),
)
self.wait()
self.play(FadeOut(arrow))