proof_engine/relativistic/
searchlight.rs1use glam::{Vec3, Vec4};
4use super::lorentz::lorentz_factor;
5
6pub fn relativistic_beaming(intensity_rest: f64, v: f64, c: f64, angle: f64) -> f64 {
10 let beta = v / c;
11 let gamma = lorentz_factor(v, c);
12 let d = gamma * (1.0 - beta * angle.cos());
13 if d.abs() < 1e-15 {
14 return intensity_rest * 1e15;
15 }
16 intensity_rest / (d * d * d)
17}
18
19pub fn aberration_angle(theta_rest: f64, v: f64, c: f64) -> f64 {
22 let beta = v / c;
23 let cos_rest = theta_rest.cos();
24 let cos_obs = (cos_rest + beta) / (1.0 + beta * cos_rest);
25 cos_obs.clamp(-1.0, 1.0).acos()
26}
27
28pub fn headlight_factor(v: f64, c: f64) -> f64 {
32 let beta = v / c;
33 let gamma = lorentz_factor(v, c);
34 let d = 1.0 / (gamma * (1.0 - beta));
35 d * d * d
36}
37
38#[derive(Debug, Clone)]
40pub struct SearchlightRenderer {
41 pub c: f64,
42 pub observer_pos: Vec3,
43 pub max_boost: f32,
44}
45
46impl SearchlightRenderer {
47 pub fn new(c: f64, observer_pos: Vec3) -> Self {
48 Self {
49 c,
50 observer_pos,
51 max_boost: 100.0,
52 }
53 }
54
55 pub fn entity_brightness(
57 &self,
58 entity_pos: Vec3,
59 entity_velocity: Vec3,
60 base_luminosity: f32,
61 ) -> f32 {
62 let v = entity_velocity.length() as f64;
63 if v < 1e-10 {
64 return base_luminosity;
65 }
66
67 let to_observer = (self.observer_pos - entity_pos).normalize_or_zero();
68 let vel_dir = entity_velocity.normalize();
69 let cos_angle = vel_dir.dot(to_observer) as f64;
70 let angle = cos_angle.clamp(-1.0, 1.0).acos();
71
72 let beamed = relativistic_beaming(base_luminosity as f64, v, self.c, angle);
73 (beamed as f32).min(self.max_boost * base_luminosity)
74 }
75
76 pub fn batch_brightness(
78 &self,
79 entities: &[(Vec3, Vec3, f32)], ) -> Vec<f32> {
81 entities.iter().map(|(pos, vel, lum)| {
82 self.entity_brightness(*pos, *vel, *lum)
83 }).collect()
84 }
85
86 pub fn beamed_color(
88 &self,
89 entity_pos: Vec3,
90 entity_velocity: Vec3,
91 base_color: Vec4,
92 ) -> Vec4 {
93 let factor = self.entity_brightness(entity_pos, entity_velocity, 1.0);
94 Vec4::new(
95 (base_color.x * factor).min(1.0),
96 (base_color.y * factor).min(1.0),
97 (base_color.z * factor).min(1.0),
98 base_color.w,
99 )
100 }
101
102 pub fn beaming_half_angle(&self, v: f64) -> f64 {
104 let beta = v / self.c;
105 let gamma = lorentz_factor(v, self.c);
106 (1.0 / gamma).asin()
108 }
109}
110
111pub fn apparent_brightness(luminosity: f64, v: f64, c: f64, angle: f64) -> f64 {
114 let beta = v / c;
115 let gamma = lorentz_factor(v, c);
116 let d = 1.0 / (gamma * (1.0 - beta * angle.cos()));
117 luminosity * d * d * d
118}
119
120pub fn solid_angle_transform(d_omega_rest: f64, v: f64, c: f64, theta: f64) -> f64 {
123 let beta = v / c;
124 let gamma = lorentz_factor(v, c);
125 let denom = gamma * (1.0 - beta * theta.cos());
126 d_omega_rest / (denom * denom)
127}
128
129pub fn backward_dimming(v: f64, c: f64) -> f64 {
131 let beta = v / c;
132 let gamma = lorentz_factor(v, c);
133 let d = 1.0 / (gamma * (1.0 + beta));
134 d * d * d
135}
136
137pub fn doppler_boost_factor(v: f64, c: f64, theta: f64) -> f64 {
140 let beta = v / c;
141 let gamma = lorentz_factor(v, c);
142 1.0 / (gamma * (1.0 - beta * theta.cos()))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use std::f64::consts::PI;
149
150 const C: f64 = 299_792_458.0;
151
152 #[test]
153 fn test_forward_brightness_boost() {
154 let fwd = relativistic_beaming(1.0, 0.9 * C, C, 0.0);
155 assert!(fwd > 1.0, "Forward should be boosted: {}", fwd);
156 assert!(fwd > 100.0, "At 0.9c forward boost should be large: {}", fwd);
157 }
158
159 #[test]
160 fn test_backward_dimming() {
161 let bwd = relativistic_beaming(1.0, 0.9 * C, C, PI);
162 assert!(bwd < 1.0, "Backward should be dimmed: {}", bwd);
163 }
164
165 #[test]
166 fn test_headlight_factor_increases_with_v() {
167 let h1 = headlight_factor(0.5 * C, C);
168 let h2 = headlight_factor(0.9 * C, C);
169 assert!(h2 > h1, "Higher v should give more forward boost: {} vs {}", h1, h2);
170 }
171
172 #[test]
173 fn test_headlight_at_rest() {
174 let h = headlight_factor(0.0, C);
175 assert!((h - 1.0).abs() < 1e-6, "At rest, headlight factor = 1: {}", h);
176 }
177
178 #[test]
179 fn test_aberration_forward() {
180 let theta_obs = aberration_angle(std::f64::consts::FRAC_PI_2, 0.9 * C, C);
182 assert!(theta_obs < std::f64::consts::FRAC_PI_2, "Should be aberrated forward: {}", theta_obs);
183 }
184
185 #[test]
186 fn test_aberration_zero_velocity() {
187 let theta_obs = aberration_angle(1.0, 0.0, C);
188 assert!((theta_obs - 1.0).abs() < 1e-10);
189 }
190
191 #[test]
192 fn test_total_luminosity_conservation() {
193 let v = 0.5 * C;
197 let n = 10000;
198 let mut sum_beamed = 0.0;
199 let mut sum_rest = 0.0;
200 let dtheta = PI / n as f64;
201 for i in 0..n {
202 let theta = (i as f64 + 0.5) * dtheta;
203 let d_omega = 2.0 * PI * theta.sin() * dtheta;
204 sum_beamed += relativistic_beaming(1.0, v, C, theta) * d_omega;
205 sum_rest += 1.0 * d_omega;
206 }
207 let gamma = lorentz_factor(v, C);
212 let expected_ratio = gamma * gamma;
213 let actual_ratio = sum_beamed / sum_rest;
214 assert!(
215 (actual_ratio - expected_ratio).abs() / expected_ratio < 0.05,
216 "Luminosity ratio: {} expected: {}",
217 actual_ratio, expected_ratio
218 );
219 }
220
221 #[test]
222 fn test_solid_angle_transform() {
223 let d_omega = solid_angle_transform(1.0, 0.0, C, 0.0);
224 assert!((d_omega - 1.0).abs() < 1e-10);
225 }
226
227 #[test]
228 fn test_searchlight_renderer() {
229 let renderer = SearchlightRenderer::new(C, Vec3::new(0.0, 0.0, 0.0));
230 let bright = renderer.entity_brightness(
232 Vec3::new(10.0, 0.0, 0.0),
233 Vec3::new(-0.9 * C as f32, 0.0, 0.0),
234 1.0,
235 );
236 let dim = renderer.entity_brightness(
238 Vec3::new(10.0, 0.0, 0.0),
239 Vec3::new(0.9 * C as f32, 0.0, 0.0),
240 1.0,
241 );
242 assert!(bright > dim, "Forward should be brighter: {} vs {}", bright, dim);
243 }
244}