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