proof_engine/relativistic/
terrell.rs1use glam::{Vec2, Vec3, Vec4};
4use super::lorentz::lorentz_factor;
5
6pub fn terrell_rotation_angle(v: f64, c: f64, object_angle: f64) -> f64 {
10 let beta = v / c;
11 if beta.abs() >= 1.0 {
12 return std::f64::consts::FRAC_PI_2;
13 }
14 let arg = beta * object_angle.sin();
19 arg.clamp(-1.0, 1.0).asin()
20}
21
22pub fn apparent_position(
25 true_pos: Vec3,
26 velocity: Vec3,
27 observer: Vec3,
28 c: f64,
29 time: f64,
30) -> Vec3 {
31 let current_pos = true_pos + velocity * time as f32;
33
34 let mut t_r = time;
40 for _ in 0..20 {
41 let pos_at_tr = true_pos + velocity * t_r as f32;
42 let dist = (pos_at_tr - observer).length() as f64;
43 let t_r_new = time - dist / c;
44 if (t_r_new - t_r).abs() < 1e-12 {
45 break;
46 }
47 t_r = t_r_new;
48 }
49
50 true_pos + velocity * t_r as f32
52}
53
54pub fn retarded_time(object_pos: Vec3, observer_pos: Vec3, c: f64) -> f64 {
58 let dist = (object_pos - observer_pos).length() as f64;
59 -dist / c }
61
62#[derive(Debug, Clone)]
64pub struct TerellRenderer {
65 pub c: f64,
66 pub observer_pos: Vec3,
67}
68
69impl TerellRenderer {
70 pub fn new(c: f64, observer_pos: Vec3) -> Self {
71 Self { c, observer_pos }
72 }
73
74 pub fn apparent_vertices(
77 &self,
78 vertices: &[Vec3],
79 center: Vec3,
80 velocity: Vec3,
81 time: f64,
82 ) -> Vec<Vec3> {
83 vertices.iter().map(|v| {
84 apparent_position(*v, velocity, self.observer_pos, self.c, time)
85 }).collect()
86 }
87
88 pub fn render_object(
92 &self,
93 vertices: &[Vec3],
94 center: Vec3,
95 velocity: Vec3,
96 time: f64,
97 ) -> Vec<Vec3> {
98 let v = velocity.length() as f64;
99 if v < 1e-10 {
100 return vertices.to_vec();
101 }
102
103 let vel_dir = velocity.normalize();
104 let to_observer = (self.observer_pos - center).normalize_or_zero();
105
106 let rotation_axis = vel_dir.cross(to_observer).normalize_or_zero();
108 let sin_angle = (v / self.c) as f32;
109 let cos_angle = (1.0 - sin_angle * sin_angle).max(0.0).sqrt();
110
111 vertices.iter().map(|vert| {
113 let rel = *vert - center;
114 let rotated = rel * cos_angle
116 + rotation_axis.cross(rel) * sin_angle
117 + rotation_axis * rotation_axis.dot(rel) * (1.0 - cos_angle);
118 center + rotated
119 }).collect()
120 }
121
122 pub fn full_render(
124 &self,
125 vertices: &[Vec3],
126 center: Vec3,
127 velocity: Vec3,
128 time: f64,
129 ) -> Vec<Vec3> {
130 let retarded = self.apparent_vertices(vertices, center, velocity, time);
132 retarded
134 }
135}
136
137pub fn render_moving_cube(
141 velocity: Vec3,
142 observer: Vec3,
143 cube_vertices: &[Vec3],
144 c: f64,
145 time: f64,
146) -> Vec<Vec3> {
147 cube_vertices.iter().map(|v| {
148 apparent_position(*v, velocity, observer, c, time)
149 }).collect()
150}
151
152pub fn finite_light_speed_positions(
155 objects: &[(Vec3, Vec3)],
156 observer: Vec3,
157 c: f64,
158) -> Vec<Vec3> {
159 objects.iter().map(|(pos, vel)| {
160 let dist = (*pos - observer).length() as f64;
162 let t_delay = dist / c;
163 *pos - *vel * t_delay as f32
165 }).collect()
166}
167
168pub fn unit_cube_vertices(center: Vec3, half_size: f32) -> Vec<Vec3> {
170 let h = half_size;
171 vec![
172 center + Vec3::new(-h, -h, -h),
173 center + Vec3::new( h, -h, -h),
174 center + Vec3::new( h, h, -h),
175 center + Vec3::new(-h, h, -h),
176 center + Vec3::new(-h, -h, h),
177 center + Vec3::new( h, -h, h),
178 center + Vec3::new( h, h, h),
179 center + Vec3::new(-h, h, h),
180 ]
181}
182
183pub fn light_travel_delays(vertices: &[Vec3], observer: Vec3, c: f64) -> Vec<f64> {
185 vertices.iter().map(|v| {
186 (*v - observer).length() as f64 / c
187 }).collect()
188}
189
190pub fn sphere_appearance_deviation(
193 v: f64,
194 c: f64,
195 n_points: usize,
196 observer_distance: f64,
197) -> f64 {
198 let observer = Vec3::new(observer_distance as f32, 0.0, 0.0);
199 let center = Vec3::ZERO;
200 let velocity = Vec3::new(0.0, v as f32, 0.0);
201 let radius = 1.0_f32;
202
203 let mut apparent_radii = Vec::new();
205 for i in 0..n_points {
206 let theta = (i as f64 / n_points as f64) * std::f64::consts::TAU;
207 let point = center + Vec3::new(0.0, theta.cos() as f32, theta.sin() as f32) * radius;
208
209 let app = apparent_position(point, velocity, observer, c, 0.0);
210 let app_center = apparent_position(center, velocity, observer, c, 0.0);
211 let r = (app - app_center).length();
212 apparent_radii.push(r);
213 }
214
215 if apparent_radii.is_empty() {
216 return 0.0;
217 }
218 let mean = apparent_radii.iter().sum::<f32>() / apparent_radii.len() as f32;
219 let max_dev = apparent_radii.iter().map(|r| (r - mean).abs()).fold(0.0_f32, f32::max);
220 (max_dev / mean) as f64
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 const C: f64 = 299_792_458.0;
228
229 #[test]
230 fn test_terrell_rotation_angle_at_rest() {
231 let angle = terrell_rotation_angle(0.0, C, std::f64::consts::FRAC_PI_2);
232 assert!(angle.abs() < 1e-10, "No rotation at rest: {}", angle);
233 }
234
235 #[test]
236 fn test_terrell_rotation_angle_increases_with_v() {
237 let a1 = terrell_rotation_angle(0.5 * C, C, std::f64::consts::FRAC_PI_2);
238 let a2 = terrell_rotation_angle(0.9 * C, C, std::f64::consts::FRAC_PI_2);
239 assert!(a2 > a1, "Rotation should increase with v: {} vs {}", a1, a2);
240 }
241
242 #[test]
243 fn test_terrell_rotation_angle_at_zero_viewing_angle() {
244 let angle = terrell_rotation_angle(0.9 * C, C, 0.0);
246 assert!(angle.abs() < 1e-10, "No rotation at head-on: {}", angle);
247 }
248
249 #[test]
250 fn test_retarded_time() {
251 let t = retarded_time(
252 Vec3::new(C as f32, 0.0, 0.0),
253 Vec3::ZERO,
254 C,
255 );
256 assert!((t - (-1.0)).abs() < 1e-6, "Retarded time offset: {}", t);
258 }
259
260 #[test]
261 fn test_apparent_position_stationary() {
262 let app = apparent_position(
263 Vec3::new(10.0, 0.0, 0.0),
264 Vec3::ZERO,
265 Vec3::ZERO,
266 C,
267 0.0,
268 );
269 assert!((app.x - 10.0).abs() < 0.01);
271 }
272
273 #[test]
274 fn test_finite_light_speed_positions() {
275 let objects = vec![
276 (Vec3::new(10.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
277 (Vec3::new(20.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
278 ];
279 let apparent = finite_light_speed_positions(&objects, Vec3::ZERO, C);
280 assert!(apparent[0].x < 10.0);
282 assert!(apparent[1].x < 20.0);
283 let shift_0 = 10.0 - apparent[0].x;
285 let shift_1 = 20.0 - apparent[1].x;
286 assert!(shift_1 > shift_0);
287 }
288
289 #[test]
290 fn test_render_moving_cube() {
291 let verts = unit_cube_vertices(Vec3::ZERO, 1.0);
292 assert_eq!(verts.len(), 8);
293 let velocity = Vec3::new(0.5 * C as f32, 0.0, 0.0);
294 let observer = Vec3::new(100.0, 0.0, 0.0);
295 let apparent = render_moving_cube(velocity, observer, &verts, C, 0.0);
296 assert_eq!(apparent.len(), 8);
297 }
298
299 #[test]
300 fn test_light_travel_delays() {
301 let verts = vec![
302 Vec3::new(10.0, 0.0, 0.0),
303 Vec3::new(20.0, 0.0, 0.0),
304 ];
305 let delays = light_travel_delays(&verts, Vec3::ZERO, C);
306 assert!(delays[1] > delays[0]);
307 assert!((delays[0] - 10.0 / C).abs() < 1e-10);
308 }
309}