1use glam::{Vec2, Vec3, Vec4};
4
5pub fn lorentz_factor(v: f64, c: f64) -> f64 {
8 let beta = (v / c).abs();
9 if beta >= 1.0 {
10 return f64::INFINITY;
11 }
12 1.0 / (1.0 - beta * beta).sqrt()
13}
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct FourVector {
18 pub t: f64,
20 pub x: f64,
21 pub y: f64,
22 pub z: f64,
23}
24
25impl FourVector {
26 pub fn new(t: f64, x: f64, y: f64, z: f64) -> Self {
27 Self { t, x, y, z }
28 }
29
30 pub fn zero() -> Self {
31 Self { t: 0.0, x: 0.0, y: 0.0, z: 0.0 }
32 }
33
34 pub fn dot(&self, other: &FourVector) -> f64 {
36 self.t * other.t - self.x * other.x - self.y * other.y - self.z * other.z
37 }
38
39 pub fn norm_sq(&self) -> f64 {
41 self.dot(self)
42 }
43
44 pub fn norm(&self) -> f64 {
46 let ns = self.norm_sq();
47 if ns >= 0.0 {
48 ns.sqrt()
49 } else {
50 -(-ns).sqrt()
51 }
52 }
53
54 pub fn spatial(&self) -> Vec3 {
56 Vec3::new(self.x as f32, self.y as f32, self.z as f32)
57 }
58
59 pub fn spatial_magnitude(&self) -> f64 {
61 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
62 }
63
64 pub fn boost(&self, velocity: Vec3, c: f64) -> FourVector {
66 boost(self, velocity, c)
67 }
68
69 pub fn scale(&self, s: f64) -> FourVector {
71 FourVector::new(self.t * s, self.x * s, self.y * s, self.z * s)
72 }
73
74 pub fn add(&self, other: &FourVector) -> FourVector {
76 FourVector::new(
77 self.t + other.t,
78 self.x + other.x,
79 self.y + other.y,
80 self.z + other.z,
81 )
82 }
83
84 pub fn sub(&self, other: &FourVector) -> FourVector {
86 FourVector::new(
87 self.t - other.t,
88 self.x - other.x,
89 self.y - other.y,
90 self.z - other.z,
91 )
92 }
93}
94
95impl std::ops::Add for FourVector {
96 type Output = FourVector;
97 fn add(self, rhs: FourVector) -> FourVector {
98 FourVector::new(self.t + rhs.t, self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
99 }
100}
101
102impl std::ops::Sub for FourVector {
103 type Output = FourVector;
104 fn sub(self, rhs: FourVector) -> FourVector {
105 FourVector::new(self.t - rhs.t, self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
106 }
107}
108
109impl std::ops::Mul<f64> for FourVector {
110 type Output = FourVector;
111 fn mul(self, rhs: f64) -> FourVector {
112 FourVector::new(self.t * rhs, self.x * rhs, self.y * rhs, self.z * rhs)
113 }
114}
115
116impl std::ops::Neg for FourVector {
117 type Output = FourVector;
118 fn neg(self) -> FourVector {
119 FourVector::new(-self.t, -self.x, -self.y, -self.z)
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct LorentzBoost {
126 pub velocity: Vec3,
127 pub gamma: f64,
128}
129
130impl LorentzBoost {
131 pub fn new(velocity: Vec3, c: f64) -> Self {
132 let v = (velocity.length() as f64).min(c * 0.9999999);
133 Self {
134 velocity,
135 gamma: lorentz_factor(v, c),
136 }
137 }
138
139 pub fn beta(&self, c: f64) -> f64 {
140 self.velocity.length() as f64 / c
141 }
142
143 pub fn apply(&self, fv: &FourVector, c: f64) -> FourVector {
145 boost(fv, self.velocity, c)
146 }
147
148 pub fn inverse(&self) -> LorentzBoost {
150 LorentzBoost {
151 velocity: -self.velocity,
152 gamma: self.gamma,
153 }
154 }
155}
156
157pub fn boost(four_vec: &FourVector, velocity: Vec3, c: f64) -> FourVector {
169 let vx = velocity.x as f64;
170 let vy = velocity.y as f64;
171 let vz = velocity.z as f64;
172 let v_mag = (vx * vx + vy * vy + vz * vz).sqrt();
173
174 if v_mag < 1e-15 {
175 return *four_vec;
176 }
177
178 let gamma = lorentz_factor(v_mag, c);
179 let nx = vx / v_mag;
180 let ny = vy / v_mag;
181 let nz = vz / v_mag;
182 let (bx, by, bz) = (vx / c, vy / c, vz / c);
183
184 let r_dot_n = four_vec.x * nx + four_vec.y * ny + four_vec.z * nz;
185 let r_dot_b = four_vec.x * bx + four_vec.y * by + four_vec.z * bz;
186
187 let t_prime = gamma * (four_vec.t - r_dot_b);
188 let coeff = (gamma - 1.0) * r_dot_n;
189 let x_prime = four_vec.x + coeff * nx - gamma * bx * four_vec.t;
190 let y_prime = four_vec.y + coeff * ny - gamma * by * four_vec.t;
191 let z_prime = four_vec.z + coeff * nz - gamma * bz * four_vec.t;
192
193 FourVector::new(t_prime, x_prime, y_prime, z_prime)
194}
195
196pub fn contract_length(proper_length: f64, v: f64, c: f64) -> f64 {
198 let gamma = lorentz_factor(v, c);
199 proper_length / gamma
200}
201
202pub fn proper_time(coordinate_time: f64, v: f64, c: f64) -> f64 {
204 let gamma = lorentz_factor(v, c);
205 coordinate_time / gamma
206}
207
208pub fn velocity_addition(v1: f64, v2: f64, c: f64) -> f64 {
210 (v1 + v2) / (1.0 + v1 * v2 / (c * c))
211}
212
213pub fn rapidity(v: f64, c: f64) -> f64 {
215 (v / c).atanh()
216}
217
218pub fn four_momentum(mass: f64, velocity: Vec3, c: f64) -> FourVector {
221 let vx = velocity.x as f64;
222 let vy = velocity.y as f64;
223 let vz = velocity.z as f64;
224 let v_mag = (vx * vx + vy * vy + vz * vz).sqrt();
225 let gamma = lorentz_factor(v_mag, c);
226 FourVector::new(
227 gamma * mass * c,
228 gamma * mass * vx,
229 gamma * mass * vy,
230 gamma * mass * vz,
231 )
232}
233
234pub fn relativistic_energy(mass: f64, v: f64, c: f64) -> f64 {
236 lorentz_factor(v, c) * mass * c * c
237}
238
239pub fn relativistic_momentum(mass: f64, v: f64, c: f64) -> f64 {
241 lorentz_factor(v, c) * mass * v
242}
243
244pub fn invariant_mass(energy: f64, momentum: f64, c: f64) -> f64 {
247 let m_sq = energy * energy / (c * c * c * c) - momentum * momentum / (c * c);
248 if m_sq >= 0.0 {
249 m_sq.sqrt()
250 } else {
251 0.0
252 }
253}
254
255#[derive(Debug, Clone)]
258pub struct LorentzContractor {
259 pub c: f64,
260}
261
262impl LorentzContractor {
263 pub fn new(c: f64) -> Self {
264 Self { c }
265 }
266
267 pub fn contracted_scale(&self, velocity: Vec3) -> Vec3 {
270 let v = velocity.length() as f64;
271 if v < 1e-12 {
272 return Vec3::ONE;
273 }
274 let gamma = lorentz_factor(v, self.c);
275 let contraction = (1.0 / gamma) as f32;
276 let dir = velocity.normalize();
277
278 let sx = 1.0 + (contraction - 1.0) * dir.x * dir.x;
282 let sy = 1.0 + (contraction - 1.0) * dir.y * dir.y;
283 let sz = 1.0 + (contraction - 1.0) * dir.z * dir.z;
284
285 Vec3::new(sx, sy, sz)
286 }
287
288 pub fn contract_vertices(&self, vertices: &[Vec3], center: Vec3, velocity: Vec3) -> Vec<Vec3> {
290 let v = velocity.length() as f64;
291 if v < 1e-12 {
292 return vertices.to_vec();
293 }
294 let gamma = lorentz_factor(v, self.c);
295 let contraction = (1.0 / gamma) as f32;
296 let dir = velocity.normalize();
297
298 vertices.iter().map(|vert| {
299 let rel = *vert - center;
300 let along = rel.dot(dir) * dir;
301 let perp = rel - along;
302 center + along * contraction + perp
303 }).collect()
304 }
305}
306
307#[derive(Debug, Clone)]
309pub struct LorentzRenderer {
310 pub c: f64,
311 pub contraction_enabled: bool,
312 pub color_shift_enabled: bool,
313}
314
315impl LorentzRenderer {
316 pub fn new(c: f64) -> Self {
317 Self {
318 c,
319 contraction_enabled: true,
320 color_shift_enabled: false,
321 }
322 }
323
324 pub fn apparent_vertex(
327 &self,
328 vertex: Vec3,
329 object_center: Vec3,
330 velocity: Vec3,
331 ) -> Vec3 {
332 if !self.contraction_enabled {
333 return vertex;
334 }
335 let v = velocity.length() as f64;
336 if v < 1e-12 {
337 return vertex;
338 }
339 let gamma = lorentz_factor(v, self.c);
340 let contraction = (1.0 / gamma) as f32;
341 let dir = velocity.normalize();
342 let rel = vertex - object_center;
343 let along = rel.dot(dir) * dir;
344 let perp = rel - along;
345 object_center + along * contraction + perp
346 }
347
348 pub fn render_glyphs(
350 &self,
351 positions: &[Vec3],
352 center: Vec3,
353 velocity: Vec3,
354 ) -> Vec<Vec3> {
355 positions.iter().map(|p| self.apparent_vertex(*p, center, velocity)).collect()
356 }
357
358 pub fn brightness_factor(&self, velocity: Vec3, observer_dir: Vec3) -> f32 {
361 let v = velocity.length() as f64;
362 if v < 1e-12 {
363 return 1.0;
364 }
365 let gamma = lorentz_factor(v, self.c);
366 let beta = v / self.c;
367 let dir = velocity.normalize();
368 let cos_theta = dir.dot(observer_dir.normalize()) as f64;
369 let doppler = gamma * (1.0 - beta * cos_theta);
370 if doppler > 1e-10 {
371 (1.0 / doppler).powi(3) as f32
372 } else {
373 1.0
374 }
375 }
376
377 pub fn transform_entity(
380 &self,
381 positions: &[Vec3],
382 center: Vec3,
383 velocity: Vec3,
384 observer_pos: Vec3,
385 ) -> (Vec<Vec3>, Vec<f32>) {
386 let new_positions = self.render_glyphs(positions, center, velocity);
387 let observer_dir = (observer_pos - center).normalize_or_zero();
388 let brightness = positions.iter().map(|_| self.brightness_factor(velocity, observer_dir)).collect();
389 (new_positions, brightness)
390 }
391}
392
393pub fn kinetic_energy(mass: f64, v: f64, c: f64) -> f64 {
395 (lorentz_factor(v, c) - 1.0) * mass * c * c
396}
397
398pub fn velocity_from_rapidity(phi: f64, c: f64) -> f64 {
400 c * phi.tanh()
401}
402
403pub fn compose_collinear_boosts(v1: f64, v2: f64, c: f64) -> f64 {
405 let phi1 = rapidity(v1, c);
406 let phi2 = rapidity(v2, c);
407 velocity_from_rapidity(phi1 + phi2, c)
408}
409
410pub fn doppler_factor(v: f64, c: f64, approaching: bool) -> f64 {
412 let beta = v / c;
413 if approaching {
414 ((1.0 + beta) / (1.0 - beta)).sqrt()
415 } else {
416 ((1.0 - beta) / (1.0 + beta)).sqrt()
417 }
418}
419
420pub fn four_velocity(velocity: Vec3, c: f64) -> FourVector {
422 let vx = velocity.x as f64;
423 let vy = velocity.y as f64;
424 let vz = velocity.z as f64;
425 let v_mag = (vx * vx + vy * vy + vz * vz).sqrt();
426 let gamma = lorentz_factor(v_mag, c);
427 FourVector::new(gamma * c, gamma * vx, gamma * vy, gamma * vz)
428}
429
430pub fn is_timelike(fv: &FourVector) -> bool {
432 fv.norm_sq() > 0.0
433}
434
435pub fn is_spacelike(fv: &FourVector) -> bool {
437 fv.norm_sq() < 0.0
438}
439
440pub fn is_lightlike(fv: &FourVector, tolerance: f64) -> bool {
442 fv.norm_sq().abs() < tolerance
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 const C: f64 = 299_792_458.0; #[test]
452 fn test_lorentz_factor_zero() {
453 let g = lorentz_factor(0.0, C);
454 assert!((g - 1.0).abs() < 1e-10);
455 }
456
457 #[test]
458 fn test_lorentz_factor_high_v() {
459 let g = lorentz_factor(0.99 * C, C);
460 let expected = 1.0 / (1.0 - 0.99 * 0.99_f64).sqrt();
461 assert!((g - expected).abs() < 1e-6);
462 }
463
464 #[test]
465 fn test_lorentz_factor_approaches_infinity() {
466 let g = lorentz_factor(0.9999999 * C, C);
467 assert!(g > 1000.0);
468 let g2 = lorentz_factor(C, C);
469 assert!(g2.is_infinite());
470 }
471
472 #[test]
473 fn test_four_vector_minkowski_dot() {
474 let a = FourVector::new(5.0, 1.0, 2.0, 3.0);
475 let b = FourVector::new(3.0, 1.0, 1.0, 1.0);
476 assert!((a.dot(&b) - 9.0).abs() < 1e-10);
478 }
479
480 #[test]
481 fn test_four_vector_norm_sq() {
482 let v = FourVector::new(5.0, 3.0, 0.0, 0.0);
483 assert!((v.norm_sq() - 16.0).abs() < 1e-10);
485 }
486
487 #[test]
488 fn test_boost_zero_velocity() {
489 let fv = FourVector::new(1.0, 2.0, 3.0, 4.0);
490 let boosted = boost(&fv, Vec3::ZERO, C);
491 assert!((boosted.t - fv.t).abs() < 1e-10);
492 assert!((boosted.x - fv.x).abs() < 1e-10);
493 }
494
495 #[test]
496 fn test_boost_preserves_interval() {
497 let fv = FourVector::new(10.0, 1.0, 2.0, 0.0);
498 let original_interval = fv.norm_sq();
499 let boosted = boost(&fv, Vec3::new(0.5 * C as f32, 0.0, 0.0), C);
500 let boosted_interval = boosted.norm_sq();
501 assert!(
502 (original_interval - boosted_interval).abs() < 1e-3,
503 "Interval not preserved: {} vs {}",
504 original_interval,
505 boosted_interval
506 );
507 }
508
509 #[test]
510 fn test_contract_length() {
511 let L0 = 10.0;
512 let L = contract_length(L0, 0.0, C);
513 assert!((L - 10.0).abs() < 1e-10);
514
515 let L2 = contract_length(L0, 0.866 * C, C);
516 assert!((L2 - 5.0).abs() < 0.1);
518 }
519
520 #[test]
521 fn test_velocity_addition_subluminal() {
522 let w = velocity_addition(0.5 * C, 0.5 * C, C);
523 assert!(w < C);
524 let expected = (0.5 * C + 0.5 * C) / (1.0 + 0.25);
525 assert!((w - expected).abs() < 1e-6);
526 }
527
528 #[test]
529 fn test_velocity_addition_light_speed() {
530 let w = velocity_addition(C, 0.5 * C, C);
532 assert!((w - C).abs() < 1e-6);
533 }
534
535 #[test]
536 fn test_rapidity() {
537 let phi = rapidity(0.0, C);
538 assert!(phi.abs() < 1e-10);
539
540 let phi2 = rapidity(0.5 * C, C);
541 assert!((phi2 - (0.5_f64).atanh()).abs() < 1e-10);
542 }
543
544 #[test]
545 fn test_energy_momentum_relation() {
546 let mass = 1.0;
548 let v = 0.8 * C;
549 let E = relativistic_energy(mass, v, C);
550 let p = relativistic_momentum(mass, v, C);
551 let lhs = E * E;
552 let rhs = p * p * C * C + mass * mass * C * C * C * C;
553 assert!(
554 (lhs - rhs).abs() / lhs < 1e-10,
555 "E^2 = p^2 c^2 + m^2 c^4 failed: {} vs {}",
556 lhs, rhs
557 );
558 }
559
560 #[test]
561 fn test_invariant_mass_recovery() {
562 let mass = 2.5;
563 let v = 0.6 * C;
564 let E = relativistic_energy(mass, v, C);
565 let p = relativistic_momentum(mass, v, C);
566 let m_recovered = invariant_mass(E, p, C);
567 assert!((m_recovered - mass).abs() < 1e-6);
568 }
569
570 #[test]
571 fn test_four_momentum_norm() {
572 let mass = 1.0;
573 let vel = Vec3::new(0.3 * C as f32, 0.4 * C as f32, 0.0);
574 let pm = four_momentum(mass, vel, C);
575 let norm = pm.norm_sq();
577 let expected = mass * mass * C * C;
578 assert!(
579 (norm - expected).abs() / expected < 1e-6,
580 "Four-momentum norm: {} vs {}",
581 norm, expected
582 );
583 }
584
585 #[test]
586 fn test_proper_time() {
587 let t = 10.0;
588 let tau = proper_time(t, 0.866 * C, C);
589 assert!((tau - 5.0).abs() < 0.1);
591 }
592
593 #[test]
594 fn test_lorentz_contraction_renderer() {
595 let contactor = LorentzContractor::new(C);
596 let scale = contactor.contracted_scale(Vec3::new(0.866 * C as f32, 0.0, 0.0));
597 assert!((scale.x - 0.5).abs() < 0.05);
599 assert!((scale.y - 1.0).abs() < 0.01);
600 assert!((scale.z - 1.0).abs() < 0.01);
601 }
602
603 #[test]
604 fn test_compose_collinear_boosts() {
605 let w = compose_collinear_boosts(0.5 * C, 0.5 * C, C);
606 let w2 = velocity_addition(0.5 * C, 0.5 * C, C);
607 assert!((w - w2).abs() < 1e-6);
608 }
609
610 #[test]
611 fn test_kinetic_energy_low_v() {
612 let mass = 1.0;
614 let v = 0.001 * C;
615 let ke_rel = kinetic_energy(mass, v, C);
616 let ke_classical = 0.5 * mass * v * v;
617 assert!(
618 (ke_rel - ke_classical).abs() / ke_classical < 0.01,
619 "Low v KE: rel={} classical={}",
620 ke_rel, ke_classical
621 );
622 }
623
624 #[test]
625 fn test_four_velocity_norm() {
626 let vel = Vec3::new(0.5 * C as f32, 0.0, 0.0);
627 let u = four_velocity(vel, C);
628 let norm = u.norm_sq();
630 assert!(
631 (norm - C * C).abs() / (C * C) < 1e-6,
632 "Four-velocity norm: {} vs {}",
633 norm, C * C
634 );
635 }
636}