1use glam::{Vec3, Vec4};
5use std::f32::consts::PI;
6
7const C: f32 = 1.0;
9
10#[derive(Clone, Debug)]
14pub struct PlaneWave {
15 pub direction: Vec3, pub polarization: Vec3, pub frequency: f32,
18 pub amplitude: f32,
19 pub phase: f32,
20}
21
22impl PlaneWave {
23 pub fn new(direction: Vec3, polarization: Vec3, frequency: f32, amplitude: f32) -> Self {
24 Self {
25 direction: direction.normalize(),
26 polarization: polarization.normalize(),
27 frequency,
28 amplitude,
29 phase: 0.0,
30 }
31 }
32
33 pub fn evaluate(&self, pos: Vec3, time: f32) -> (Vec3, Vec3) {
37 let omega = 2.0 * PI * self.frequency;
38 let k = omega / C;
39 let k_vec = self.direction * k;
40 let phase = k_vec.dot(pos) - omega * time + self.phase;
41 let e_mag = self.amplitude * phase.sin();
42 let e = self.polarization * e_mag;
43 let b = self.direction.cross(e) / C;
44 (e, b)
45 }
46
47 pub fn wavelength(&self) -> f32 {
49 C / self.frequency
50 }
51
52 pub fn wavenumber(&self) -> f32 {
54 2.0 * PI * self.frequency / C
55 }
56
57 pub fn angular_frequency(&self) -> f32 {
59 2.0 * PI * self.frequency
60 }
61
62 pub fn intensity(&self) -> f32 {
64 0.5 * self.amplitude * self.amplitude
65 }
66}
67
68#[derive(Clone, Debug)]
72pub struct SphericalWave {
73 pub origin: Vec3,
74 pub frequency: f32,
75 pub amplitude: f32,
76}
77
78impl SphericalWave {
79 pub fn new(origin: Vec3, frequency: f32, amplitude: f32) -> Self {
80 Self { origin, frequency, amplitude }
81 }
82
83 pub fn evaluate_scalar(&self, pos: Vec3, time: f32) -> f32 {
86 let r_vec = pos - self.origin;
87 let r = r_vec.length();
88 if r < 1e-10 {
89 return 0.0;
90 }
91 let omega = 2.0 * PI * self.frequency;
92 let k = omega / C;
93 (self.amplitude / r) * (k * r - omega * time).sin()
94 }
95
96 pub fn evaluate(&self, pos: Vec3, time: f32) -> (Vec3, Vec3) {
98 let r_vec = pos - self.origin;
99 let r = r_vec.length();
100 if r < 1e-10 {
101 return (Vec3::ZERO, Vec3::ZERO);
102 }
103 let r_hat = r_vec / r;
104 let omega = 2.0 * PI * self.frequency;
105 let k = omega / C;
106 let scalar = (self.amplitude / r) * (k * r - omega * time).sin();
107
108 let e_dir = if r_hat.x.abs() < 0.9 {
110 Vec3::X.cross(r_hat).normalize()
111 } else {
112 Vec3::Y.cross(r_hat).normalize()
113 };
114 let e = e_dir * scalar;
115 let b = r_hat.cross(e) / C;
116 (e, b)
117 }
118}
119
120#[derive(Clone, Debug)]
124pub struct GaussianBeam {
125 pub origin: Vec3,
126 pub direction: Vec3,
127 pub waist: f32, pub wavelength: f32,
129}
130
131impl GaussianBeam {
132 pub fn new(origin: Vec3, direction: Vec3, waist: f32, wavelength: f32) -> Self {
133 Self {
134 origin,
135 direction: direction.normalize(),
136 waist,
137 wavelength,
138 }
139 }
140
141 pub fn rayleigh_range(&self) -> f32 {
143 PI * self.waist * self.waist / self.wavelength
144 }
145
146 pub fn beam_radius(&self, z: f32) -> f32 {
148 let zr = self.rayleigh_range();
149 self.waist * (1.0 + (z / zr).powi(2)).sqrt()
150 }
151
152 pub fn intensity_at(&self, pos: Vec3) -> f32 {
154 let to_point = pos - self.origin;
155 let z = to_point.dot(self.direction);
156 let perp = to_point - z * self.direction;
157 let rho = perp.length();
158
159 let w = self.beam_radius(z);
160 let w0 = self.waist;
161 let intensity = (w0 / w) * (w0 / w) * (-2.0 * rho * rho / (w * w)).exp();
162 intensity
163 }
164
165 pub fn evaluate(&self, pos: Vec3, time: f32) -> f32 {
167 let to_point = pos - self.origin;
168 let z = to_point.dot(self.direction);
169 let perp = to_point - z * self.direction;
170 let rho = perp.length();
171
172 let zr = self.rayleigh_range();
173 let w = self.beam_radius(z);
174 let k = 2.0 * PI / self.wavelength;
175 let omega = 2.0 * PI * C / self.wavelength;
176
177 let amplitude = (self.waist / w) * (-rho * rho / (w * w)).exp();
178 let gouy_phase = (z / zr).atan();
179 let phase = k * z - omega * time + k * rho * rho * z / (2.0 * (z * z + zr * zr)) - gouy_phase;
180
181 amplitude * phase.cos()
182 }
183}
184
185#[derive(Clone, Debug)]
189pub struct WavePacket {
190 pub center_freq: f32,
191 pub bandwidth: f32,
192 pub group_velocity: f32,
193}
194
195impl WavePacket {
196 pub fn new(center_freq: f32, bandwidth: f32, group_velocity: f32) -> Self {
197 Self { center_freq, bandwidth, group_velocity }
198 }
199
200 pub fn evaluate(&self, x: f32, t: f32) -> f32 {
203 let omega0 = 2.0 * PI * self.center_freq;
204 let k0 = omega0 / C;
205 let sigma = 1.0 / (2.0 * PI * self.bandwidth);
206
207 let xi = x - self.group_velocity * t;
209 let envelope = (-xi * xi / (2.0 * sigma * sigma)).exp();
210
211 let carrier = (k0 * x - omega0 * t).cos();
213
214 envelope * carrier
215 }
216
217 pub fn phase_velocity(&self) -> f32 {
219 C }
221
222 pub fn spatial_width(&self) -> f32 {
224 1.0 / (2.0 * PI * self.bandwidth)
225 }
226}
227
228pub fn standing_wave(pos: f32, time: f32, wavelength: f32, amplitude: f32) -> f32 {
232 let k = 2.0 * PI / wavelength;
233 let omega = 2.0 * PI * C / wavelength;
234 2.0 * amplitude * (k * pos).cos() * (omega * time).cos()
236}
237
238pub fn interference_pattern(wave1: &PlaneWave, wave2: &PlaneWave, points: &[Vec3], time: f32) -> Vec<f32> {
242 points.iter().map(|&pos| {
243 let (e1, _) = wave1.evaluate(pos, time);
244 let (e2, _) = wave2.evaluate(pos, time);
245 let total = e1 + e2;
246 total.length_squared() }).collect()
248}
249
250pub fn diffraction_single_slit(slit_width: f32, wavelength: f32, angle: f32) -> f32 {
255 let beta = PI * slit_width * angle.sin() / wavelength;
256 if beta.abs() < 1e-8 {
257 return 1.0; }
259 let sinc = beta.sin() / beta;
260 sinc * sinc
261}
262
263pub fn snells_law(n1: f32, n2: f32, theta_i: f32) -> Option<f32> {
268 let sin_t = n1 * theta_i.sin() / n2;
269 if sin_t.abs() > 1.0 {
270 None } else {
272 Some(sin_t.asin())
273 }
274}
275
276pub fn fresnel_coefficients(n1: f32, n2: f32, theta_i: f32) -> (f32, f32) {
279 let cos_i = theta_i.cos();
280 let sin_t = n1 * theta_i.sin() / n2;
281 if sin_t.abs() > 1.0 {
282 return (1.0, 0.0); }
284 let cos_t = (1.0 - sin_t * sin_t).sqrt();
285
286 let rs = (n1 * cos_i - n2 * cos_t) / (n1 * cos_i + n2 * cos_t);
288 let rp = (n2 * cos_i - n1 * cos_t) / (n2 * cos_i + n1 * cos_t);
290
291 let reflectance = 0.5 * (rs * rs + rp * rp);
293 let transmittance = 1.0 - reflectance;
294 (reflectance, transmittance)
295}
296
297pub struct WaveRenderer {
301 pub color_positive: Vec4,
302 pub color_negative: Vec4,
303 pub brightness_scale: f32,
304}
305
306impl WaveRenderer {
307 pub fn new() -> Self {
308 Self {
309 color_positive: Vec4::new(1.0, 0.8, 0.2, 1.0),
310 color_negative: Vec4::new(0.2, 0.4, 1.0, 1.0),
311 brightness_scale: 1.0,
312 }
313 }
314
315 pub fn color_for_amplitude(&self, amplitude: f32) -> Vec4 {
317 let normalized = (amplitude * self.brightness_scale).clamp(-1.0, 1.0);
318 let t = (normalized + 1.0) * 0.5; let color = self.color_negative * (1.0 - t) + self.color_positive * t;
320 let alpha = normalized.abs().max(0.05);
321 Vec4::new(color.x, color.y, color.z, alpha)
322 }
323
324 pub fn render_1d_wave(
326 &self,
327 wave: &PlaneWave,
328 x_range: (f32, f32),
329 num_points: usize,
330 time: f32,
331 ) -> Vec<(f32, Vec4)> {
332 let mut result = Vec::with_capacity(num_points);
333 for i in 0..num_points {
334 let t = i as f32 / (num_points - 1).max(1) as f32;
335 let x = x_range.0 + t * (x_range.1 - x_range.0);
336 let pos = wave.direction * x;
337 let (e, _) = wave.evaluate(pos, time);
338 let amp = e.dot(wave.polarization);
339 result.push((x, self.color_for_amplitude(amp)));
340 }
341 result
342 }
343
344 pub fn glyph_for_amplitude(amplitude: f32) -> char {
346 let a = amplitude.abs();
347 if a > 0.8 {
348 '█'
349 } else if a > 0.6 {
350 '▓'
351 } else if a > 0.4 {
352 '▒'
353 } else if a > 0.2 {
354 '░'
355 } else if a > 0.05 {
356 '·'
357 } else {
358 ' '
359 }
360 }
361}
362
363impl Default for WaveRenderer {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_dispersion_relation() {
377 let wave = PlaneWave::new(Vec3::X, Vec3::Y, 2.0, 1.0);
379 let omega = wave.angular_frequency();
380 let k = wave.wavenumber();
381 assert!((omega - C * k).abs() < 1e-6, "Dispersion relation: omega={}, ck={}", omega, C * k);
382 }
383
384 #[test]
385 fn test_plane_wave_orthogonality() {
386 let wave = PlaneWave::new(Vec3::X, Vec3::Y, 1.0, 1.0);
387 let (e, b) = wave.evaluate(Vec3::new(0.25, 0.0, 0.0), 0.0);
388 let dot = e.dot(b);
390 assert!(dot.abs() < 1e-6, "E·B should be 0, got {}", dot);
391 let dot_ek = e.dot(wave.direction);
393 assert!(dot_ek.abs() < 1e-6, "E·k should be 0, got {}", dot_ek);
394 let dot_bk = b.dot(wave.direction);
396 assert!(dot_bk.abs() < 1e-6, "B·k should be 0, got {}", dot_bk);
397 }
398
399 #[test]
400 fn test_plane_wave_ratio() {
401 let wave = PlaneWave::new(Vec3::X, Vec3::Y, 1.0, 3.0);
403 let (e, b) = wave.evaluate(Vec3::new(0.3, 0.0, 0.0), 0.1);
404 if b.length() > 1e-10 {
405 let ratio = e.length() / b.length();
406 assert!((ratio - C).abs() < 0.01, "|E|/|B| should be c, got {}", ratio);
407 }
408 }
409
410 #[test]
411 fn test_standing_wave_nodes() {
412 let wavelength = 2.0;
413 let node_x = wavelength / 4.0;
415 let val = standing_wave(node_x, 0.0, wavelength, 1.0);
416 assert!(val.abs() < 1e-5, "Standing wave should have node at lambda/4: {}", val);
417 }
418
419 #[test]
420 fn test_standing_wave_antinode() {
421 let wavelength = 2.0;
422 let val = standing_wave(0.0, 0.0, wavelength, 1.0);
424 assert!((val - 2.0).abs() < 1e-5, "Antinode amplitude should be 2A: {}", val);
425 }
426
427 #[test]
428 fn test_snells_law_normal_incidence() {
429 let theta_t = snells_law(1.0, 1.5, 0.0).unwrap();
430 assert!(theta_t.abs() < 1e-6, "Normal incidence should give 0 refraction angle");
431 }
432
433 #[test]
434 fn test_snells_law_tir() {
435 let critical = (1.0 / 1.5_f32).asin();
437 let result = snells_law(1.5, 1.0, critical + 0.1);
439 assert!(result.is_none(), "Should have total internal reflection");
440 }
441
442 #[test]
443 fn test_snells_law_symmetry() {
444 let theta_i = 0.5;
445 let theta_t = snells_law(1.0, 1.5, theta_i).unwrap();
446 let theta_back = snells_law(1.5, 1.0, theta_t).unwrap();
447 assert!((theta_back - theta_i).abs() < 1e-5, "Snell's law should be reversible");
448 }
449
450 #[test]
451 fn test_fresnel_normal_incidence() {
452 let (r, t) = fresnel_coefficients(1.0, 1.5, 0.0);
453 let expected_r = ((1.0 - 1.5) / (1.0 + 1.5)).powi(2);
455 assert!((r - expected_r).abs() < 0.01, "R={}, expected={}", r, expected_r);
456 assert!((r + t - 1.0).abs() < 0.01, "R+T should be 1");
457 }
458
459 #[test]
460 fn test_diffraction_central_maximum() {
461 let intensity = diffraction_single_slit(1.0, 0.5, 0.0);
462 assert!((intensity - 1.0).abs() < 1e-6, "Central max should be 1.0");
463 }
464
465 #[test]
466 fn test_diffraction_first_minimum() {
467 let a = 2.0;
469 let lambda = 0.5;
470 let angle = (lambda / a).asin();
471 let intensity = diffraction_single_slit(a, lambda, angle);
472 assert!(intensity < 0.001, "First minimum should be ~0, got {}", intensity);
473 }
474
475 #[test]
476 fn test_spherical_wave_decay() {
477 let wave = SphericalWave::new(Vec3::ZERO, 1.0, 1.0);
478 let a1 = wave.evaluate_scalar(Vec3::new(1.0, 0.0, 0.0), 0.0).abs();
479 let a2 = wave.evaluate_scalar(Vec3::new(2.0, 0.0, 0.0), 0.0).abs();
480 if a2 > 1e-10 {
482 let ratio = a1 / a2;
483 assert!((ratio - 2.0).abs() < 0.1, "1/r decay ratio: {}", ratio);
484 }
485 }
486
487 #[test]
488 fn test_gaussian_beam_waist() {
489 let beam = GaussianBeam::new(Vec3::ZERO, Vec3::X, 1.0, 0.5);
490 assert!((beam.beam_radius(0.0) - 1.0).abs() < 1e-6);
492 let zr = beam.rayleigh_range();
494 let w_zr = beam.beam_radius(zr);
495 assert!((w_zr - 1.0 * 2.0_f32.sqrt()).abs() < 0.01);
496 }
497
498 #[test]
499 fn test_wave_packet_moves() {
500 let wp = WavePacket::new(1.0, 0.1, 0.5);
501 let t = 10.0;
503 let peak_x = wp.group_velocity * t;
504 let at_peak = wp.evaluate(peak_x, t).abs();
506 let away = wp.evaluate(peak_x + 100.0, t).abs();
507 assert!(at_peak > away, "Packet should be centered at group velocity position");
508 }
509
510 #[test]
511 fn test_interference() {
512 let w1 = PlaneWave::new(Vec3::X, Vec3::Y, 1.0, 1.0);
513 let w2 = PlaneWave { phase: PI, ..w1.clone() }; let points = vec![Vec3::ZERO];
515 let pattern = interference_pattern(&w1, &w2, &points, 0.0);
516 let points2 = vec![Vec3::new(0.1, 0.0, 0.0)];
521 let p = interference_pattern(&w1, &w2, &points2, 0.0);
522 assert!(p[0] < 1.0);
524 }
525}