mirror of
https://github.com/3b1b/videos.git
synced 2025-09-18 21:38:53 +00:00
More diffraction animations
This commit is contained in:
parent
1fd090ac37
commit
2db4ee4e0c
5 changed files with 2671 additions and 301 deletions
|
@ -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
|
@ -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));
|
||||
}
|
|
@ -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()
|
||||
|
||||
|
|
45
_2024/holograms/supplements.py
Normal file
45
_2024/holograms/supplements.py
Normal 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))
|
Loading…
Add table
Reference in a new issue