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 let gamma = lorentz_factor(0.9 * C, C);
160 let d = 1.0 / (gamma * (1.0 - 0.9));
161 assert!((fwd - d * d * d).abs() < 1e-6 * d * d * d, "forward boost {}", fwd);
162 assert!(fwd > 80.0, "At 0.9c forward boost should be large: {}", fwd);
163 }
164
165 #[test]
166 fn test_backward_dimming() {
167 let bwd = relativistic_beaming(1.0, 0.9 * C, C, PI);
168 assert!(bwd < 1.0, "Backward should be dimmed: {}", bwd);
169 }
170
171 #[test]
172 fn test_headlight_factor_increases_with_v() {
173 let h1 = headlight_factor(0.5 * C, C);
174 let h2 = headlight_factor(0.9 * C, C);
175 assert!(h2 > h1, "Higher v should give more forward boost: {} vs {}", h1, h2);
176 }
177
178 #[test]
179 fn test_headlight_at_rest() {
180 let h = headlight_factor(0.0, C);
181 assert!((h - 1.0).abs() < 1e-6, "At rest, headlight factor = 1: {}", h);
182 }
183
184 #[test]
185 fn test_aberration_forward() {
186 let theta_obs = aberration_angle(std::f64::consts::FRAC_PI_2, 0.9 * C, C);
188 assert!(theta_obs < std::f64::consts::FRAC_PI_2, "Should be aberrated forward: {}", theta_obs);
189 }
190
191 #[test]
192 fn test_aberration_zero_velocity() {
193 let theta_obs = aberration_angle(1.0, 0.0, C);
194 assert!((theta_obs - 1.0).abs() < 1e-10);
195 }
196
197 #[test]
198 fn test_total_luminosity_conservation() {
199 let v = 0.5 * C;
203 let n = 10000;
204 let mut sum_beamed = 0.0;
205 let mut sum_rest = 0.0;
206 let dtheta = PI / n as f64;
207 for i in 0..n {
208 let theta = (i as f64 + 0.5) * dtheta;
209 let d_omega = 2.0 * PI * theta.sin() * dtheta;
210 sum_beamed += relativistic_beaming(1.0, v, C, theta) * d_omega;
211 sum_rest += 1.0 * d_omega;
212 }
213 let gamma = lorentz_factor(v, C);
218 let expected_ratio = gamma;
219 let actual_ratio = sum_beamed / sum_rest;
220 assert!(
221 (actual_ratio - expected_ratio).abs() / expected_ratio < 1e-3,
222 "Luminosity ratio: {} expected: {}",
223 actual_ratio, expected_ratio
224 );
225 }
226
227 #[test]
228 fn test_solid_angle_transform() {
229 let d_omega = solid_angle_transform(1.0, 0.0, C, 0.0);
230 assert!((d_omega - 1.0).abs() < 1e-10);
231 }
232
233 #[test]
234 fn test_searchlight_renderer() {
235 let renderer = SearchlightRenderer::new(C, Vec3::new(0.0, 0.0, 0.0));
236 let bright = 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 let dim = renderer.entity_brightness(
244 Vec3::new(10.0, 0.0, 0.0),
245 Vec3::new(0.9 * C as f32, 0.0, 0.0),
246 1.0,
247 );
248 assert!(bright > dim, "Forward should be brighter: {} vs {}", bright, dim);
249 }
250}