1use crate::math::{Fix128, QuatFix, Vec3Fix};
15use crate::solver::RigidBody;
16
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct BodySnapshot {
23 pub position: Vec3Fix,
25 pub rotation: QuatFix,
27 pub velocity: Vec3Fix,
29}
30
31impl BodySnapshot {
32 #[inline]
34 #[must_use]
35 pub const fn from_body(body: &RigidBody) -> Self {
36 Self {
37 position: body.position,
38 rotation: body.rotation,
39 velocity: body.velocity,
40 }
41 }
42}
43
44#[derive(Clone, Debug)]
46pub struct WorldSnapshot {
47 pub bodies: Vec<BodySnapshot>,
49}
50
51impl WorldSnapshot {
52 pub fn capture(world: &crate::solver::PhysicsWorld) -> Self {
54 Self {
55 bodies: world.bodies.iter().map(BodySnapshot::from_body).collect(),
56 }
57 }
58
59 #[inline]
61 #[must_use]
62 pub fn len(&self) -> usize {
63 self.bodies.len()
64 }
65
66 #[inline]
68 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 self.bodies.is_empty()
71 }
72}
73
74pub struct InterpolationState {
79 pub prev: WorldSnapshot,
81 pub current: WorldSnapshot,
83}
84
85impl InterpolationState {
86 #[must_use]
88 pub const fn new(prev: WorldSnapshot, current: WorldSnapshot) -> Self {
89 Self { prev, current }
90 }
91
92 #[must_use]
94 pub const fn empty() -> Self {
95 Self {
96 prev: WorldSnapshot { bodies: Vec::new() },
97 current: WorldSnapshot { bodies: Vec::new() },
98 }
99 }
100
101 pub fn push(&mut self, new_state: WorldSnapshot) {
103 self.prev = core::mem::replace(&mut self.current, new_state);
104 }
105
106 pub fn capture_and_push(&mut self, world: &crate::solver::PhysicsWorld) {
108 let new_state = WorldSnapshot::capture(world);
109 self.push(new_state);
110 }
111
112 #[must_use]
116 pub fn interpolate_position(&self, body_idx: usize, alpha: Fix128) -> Vec3Fix {
117 if body_idx >= self.current.bodies.len() || body_idx >= self.prev.bodies.len() {
118 return Vec3Fix::ZERO;
119 }
120
121 let prev = self.prev.bodies[body_idx].position;
122 let curr = self.current.bodies[body_idx].position;
123
124 lerp_vec3(prev, curr, alpha)
125 }
126
127 #[must_use]
129 pub fn interpolate_rotation(&self, body_idx: usize, alpha: Fix128) -> QuatFix {
130 if body_idx >= self.current.bodies.len() || body_idx >= self.prev.bodies.len() {
131 return QuatFix::IDENTITY;
132 }
133
134 let prev = self.prev.bodies[body_idx].rotation;
135 let curr = self.current.bodies[body_idx].rotation;
136
137 slerp(prev, curr, alpha)
138 }
139
140 #[must_use]
142 pub fn interpolate(&self, body_idx: usize, alpha: Fix128) -> (Vec3Fix, QuatFix) {
143 (
144 self.interpolate_position(body_idx, alpha),
145 self.interpolate_rotation(body_idx, alpha),
146 )
147 }
148
149 #[must_use]
151 pub fn interpolate_all(&self, alpha: Fix128) -> Vec<(Vec3Fix, QuatFix)> {
152 let count = self.current.bodies.len().min(self.prev.bodies.len());
153 (0..count).map(|i| self.interpolate(i, alpha)).collect()
154 }
155
156 #[must_use]
158 pub fn body_count(&self) -> usize {
159 self.current.bodies.len().min(self.prev.bodies.len())
160 }
161}
162
163#[inline]
165#[must_use]
166pub fn lerp_vec3(a: Vec3Fix, b: Vec3Fix, t: Fix128) -> Vec3Fix {
167 let one_minus_t = Fix128::ONE - t;
168 Vec3Fix::new(
169 a.x * one_minus_t + b.x * t,
170 a.y * one_minus_t + b.y * t,
171 a.z * one_minus_t + b.z * t,
172 )
173}
174
175#[inline]
177#[must_use]
178pub fn lerp_fix128(a: Fix128, b: Fix128, t: Fix128) -> Fix128 {
179 a * (Fix128::ONE - t) + b * t
180}
181
182#[must_use]
193pub fn slerp(a: QuatFix, b: QuatFix, t: Fix128) -> QuatFix {
194 let mut dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
195
196 let b = if dot < Fix128::ZERO {
198 dot = -dot;
199 QuatFix::new(-b.x, -b.y, -b.z, -b.w)
200 } else {
201 b
202 };
203
204 if dot > Fix128::from_ratio(999, 1000) {
205 return nlerp(a, b, t);
206 }
207
208 let one_minus_dot_sq = Fix128::ONE - dot * dot;
210 let sin_theta = if one_minus_dot_sq.is_negative() {
211 Fix128::ZERO
212 } else {
213 one_minus_dot_sq.sqrt()
214 };
215 if sin_theta.is_zero() {
216 return nlerp(a, b, t);
217 }
218 let theta = Fix128::atan2(sin_theta, dot);
219 let s0 = ((Fix128::ONE - t) * theta).sin() / sin_theta;
220 let s1 = (t * theta).sin() / sin_theta;
221 let result = QuatFix::new(
222 a.x * s0 + b.x * s1,
223 a.y * s0 + b.y * s1,
224 a.z * s0 + b.z * s1,
225 a.w * s0 + b.w * s1,
226 );
227 result.normalize()
229}
230
231fn nlerp(a: QuatFix, b: QuatFix, t: Fix128) -> QuatFix {
233 let one_minus_t = Fix128::ONE - t;
234 let result = QuatFix::new(
235 a.x * one_minus_t + b.x * t,
236 a.y * one_minus_t + b.y * t,
237 a.z * one_minus_t + b.z * t,
238 a.w * one_minus_t + b.w * t,
239 );
240 let len_sq =
241 result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w;
242 if len_sq.is_zero() {
243 return QuatFix::IDENTITY;
244 }
245 result.normalize()
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_lerp_vec3() {
254 let a = Vec3Fix::from_int(0, 0, 0);
255 let b = Vec3Fix::from_int(10, 20, 30);
256 let half = Fix128::from_ratio(5, 10);
257
258 let result = lerp_vec3(a, b, half);
259 assert_eq!(result.x.hi, 5);
260 assert_eq!(result.y.hi, 10);
261 assert_eq!(result.z.hi, 15);
262 }
263
264 #[test]
265 fn test_lerp_endpoints() {
266 let a = Vec3Fix::from_int(1, 2, 3);
267 let b = Vec3Fix::from_int(4, 5, 6);
268
269 let at_zero = lerp_vec3(a, b, Fix128::ZERO);
270 assert_eq!(at_zero.x.hi, 1);
271
272 let at_one = lerp_vec3(a, b, Fix128::ONE);
273 assert_eq!(at_one.x.hi, 4);
274 }
275
276 #[test]
277 fn test_slerp_identity() {
278 let q = QuatFix::IDENTITY;
279 let result = slerp(q, q, Fix128::from_ratio(5, 10));
280 let dot = result.w;
282 assert!(dot > Fix128::from_ratio(99, 100));
283 }
284
285 #[test]
286 fn test_nlerp() {
287 let a = QuatFix::IDENTITY;
288 let b = QuatFix::from_axis_angle(Vec3Fix::UNIT_Y, Fix128::from_ratio(1, 10));
289
290 let result = nlerp(a, b, Fix128::from_ratio(5, 10));
291 let len_sq =
293 result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w;
294 let error = (len_sq - Fix128::ONE).abs();
296 assert!(error < Fix128::from_ratio(1, 100));
297 }
298
299 #[test]
300 fn test_interpolation_state() {
301 use crate::solver::{PhysicsConfig, PhysicsWorld};
302
303 let config = PhysicsConfig::default();
304 let mut world = PhysicsWorld::new(config);
305 world.add_body(RigidBody::new_dynamic(
306 Vec3Fix::from_int(0, 10, 0),
307 Fix128::ONE,
308 ));
309
310 let snap1 = WorldSnapshot::capture(&world);
311
312 world.step(Fix128::from_ratio(1, 60));
314 let snap2 = WorldSnapshot::capture(&world);
315
316 let interp = InterpolationState::new(snap1, snap2);
317 let half = Fix128::from_ratio(5, 10);
318
319 let pos = interp.interpolate_position(0, half);
320 assert!(pos.y < Fix128::from_int(10));
322 assert!(pos.y > Fix128::from_int(-100)); }
324
325 #[test]
326 fn test_world_snapshot() {
327 use crate::solver::{PhysicsConfig, PhysicsWorld};
328
329 let config = PhysicsConfig::default();
330 let mut world = PhysicsWorld::new(config);
331 world.add_body(RigidBody::new_dynamic(
332 Vec3Fix::from_int(1, 2, 3),
333 Fix128::ONE,
334 ));
335 world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
336
337 let snap = WorldSnapshot::capture(&world);
338 assert_eq!(snap.len(), 2);
339 assert_eq!(snap.bodies[0].position.x.hi, 1);
340 }
341
342 #[test]
343 fn lerp_fix128_is_affine_in_t_with_exact_dyadic_results() {
344 let a = Fix128::from_int(-4);
345 let b = Fix128::from_int(12);
346 assert_eq!(lerp_fix128(a, b, Fix128::ZERO), a);
348 assert_eq!(lerp_fix128(a, b, Fix128::ONE), b);
349 assert_eq!(
351 lerp_fix128(a, b, Fix128::from_ratio(1, 2)),
352 Fix128::from_int(4)
353 );
354 assert_eq!(lerp_fix128(a, b, Fix128::from_ratio(1, 4)), Fix128::ZERO);
355 assert_eq!(
356 lerp_fix128(a, b, Fix128::from_ratio(3, 4)),
357 Fix128::from_int(8)
358 );
359 assert_eq!(lerp_fix128(b, b, Fix128::from_ratio(3, 7)), b);
361 let t = Fix128::from_ratio(3, 8);
363 assert_eq!(lerp_fix128(a, b, t), lerp_fix128(b, a, Fix128::ONE - t));
364 let va = Vec3Fix::new(a, Fix128::ZERO, b);
366 let vb = Vec3Fix::new(b, Fix128::from_int(8), a);
367 let v = lerp_vec3(va, vb, t);
368 assert_eq!(v.x, lerp_fix128(a, b, t));
369 assert_eq!(v.y, lerp_fix128(Fix128::ZERO, Fix128::from_int(8), t));
370 assert_eq!(v.z, lerp_fix128(b, a, t));
371 assert_eq!(v.y, Fix128::from_int(3));
372 assert_eq!(lerp_fix128(a, b, Fix128::from_int(2)), Fix128::from_int(28));
374 }
375
376 #[test]
377 fn capture_and_push_shifts_current_into_prev_and_interpolate_all_gives_midpoints() {
378 use crate::solver::{PhysicsConfig, PhysicsWorld};
379
380 let config = PhysicsConfig {
382 gravity: Vec3Fix::ZERO,
383 damping: Fix128::ONE,
384 ..PhysicsConfig::default()
385 };
386 let mut world = PhysicsWorld::new(config);
387 world.add_body(
388 RigidBody::new_dynamic(Vec3Fix::from_int(0, 0, 0), Fix128::ONE)
389 .with_velocity(Vec3Fix::from_int(8, 0, 0)),
390 );
391 world.add_body(
392 RigidBody::new_dynamic(Vec3Fix::from_int(0, 10, 0), Fix128::ONE)
393 .with_velocity(Vec3Fix::from_int(0, -4, 16)),
394 );
395 world.add_body(RigidBody::new_static(Vec3Fix::from_int(5, 5, 5)));
396
397 let mut interp = InterpolationState::empty();
398 assert_eq!(interp.body_count(), 0);
399 assert!(interp.interpolate_all(Fix128::from_ratio(1, 2)).is_empty());
400
401 interp.capture_and_push(&world);
403 assert_eq!(interp.current.len(), 3);
404 assert!(interp.prev.is_empty());
405 assert_eq!(interp.body_count(), 0);
406 assert!(interp.interpolate_all(Fix128::from_ratio(1, 2)).is_empty());
407 let first = interp.current.clone();
408
409 world.step(Fix128::from_ratio(1, 4));
411 interp.capture_and_push(&world);
412 assert_eq!(interp.prev.bodies, first.bodies);
413 assert_eq!(
414 interp.current.bodies[0].position,
415 Vec3Fix::from_int(2, 0, 0)
416 );
417 assert_eq!(
418 interp.current.bodies[1].position,
419 Vec3Fix::from_int(0, 9, 4)
420 );
421 assert_eq!(
422 interp.current.bodies[2].position,
423 Vec3Fix::from_int(5, 5, 5)
424 );
425 assert_eq!(interp.body_count(), 3);
426
427 let mid = interp.interpolate_all(Fix128::from_ratio(1, 2));
429 assert_eq!(mid.len(), 3);
430 assert_eq!(mid[0].0, Vec3Fix::from_int(1, 0, 0));
431 assert_eq!(
432 mid[1].0,
433 Vec3Fix::new(Fix128::ZERO, Fix128::from_ratio(19, 2), Fix128::from_int(2))
434 );
435 assert_eq!(mid[2].0, Vec3Fix::from_int(5, 5, 5));
436 for (i, (p, q)) in mid.iter().enumerate() {
437 assert_eq!(*p, interp.interpolate_position(i, Fix128::from_ratio(1, 2)));
438 assert_eq!(*q, QuatFix::IDENTITY);
440 }
441 let at_prev = interp.interpolate_all(Fix128::ZERO);
442 let at_curr = interp.interpolate_all(Fix128::ONE);
443 for i in 0..3 {
444 assert_eq!(at_prev[i].0, interp.prev.bodies[i].position);
445 assert_eq!(at_curr[i].0, interp.current.bodies[i].position);
446 }
447
448 world.add_body(RigidBody::new_static(Vec3Fix::from_int(9, 9, 9)));
450 interp.capture_and_push(&world);
451 assert_eq!(interp.current.len(), 4);
452 assert_eq!(interp.prev.len(), 3);
453 assert_eq!(interp.body_count(), 3);
454 assert_eq!(interp.interpolate_all(Fix128::from_ratio(1, 2)).len(), 3);
455 assert_eq!(
457 interp.interpolate(3, Fix128::from_ratio(1, 2)),
458 (Vec3Fix::ZERO, QuatFix::IDENTITY)
459 );
460 }
461}