Skip to main content

alice_physics/
interpolation.rs

1//! Substep Interpolation Output
2//!
3//! Provides smooth rendering positions between physics substeps.
4//! Physics runs at fixed timestep; rendering interpolates between
5//! the previous and current physics state using alpha blending.
6//!
7//! # Usage
8//!
9//! ```rust,ignore
10//! let alpha = accumulator / fixed_dt; // 0.0..1.0
11//! let render_pos = interpolation::lerp_state(&prev_state, &current_state, alpha);
12//! ```
13
14use crate::math::{Fix128, QuatFix, Vec3Fix};
15use crate::solver::RigidBody;
16
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20/// Snapshot of a single body's render-relevant state
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct BodySnapshot {
23    /// Position
24    pub position: Vec3Fix,
25    /// Rotation
26    pub rotation: QuatFix,
27    /// Linear velocity (for extrapolation)
28    pub velocity: Vec3Fix,
29}
30
31impl BodySnapshot {
32    /// Capture snapshot from a rigid body
33    #[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/// World snapshot for interpolation
45#[derive(Clone, Debug)]
46pub struct WorldSnapshot {
47    /// Per-body snapshots
48    pub bodies: Vec<BodySnapshot>,
49}
50
51impl WorldSnapshot {
52    /// Capture snapshot from physics world
53    pub fn capture(world: &crate::solver::PhysicsWorld) -> Self {
54        Self {
55            bodies: world.bodies.iter().map(BodySnapshot::from_body).collect(),
56        }
57    }
58
59    /// Number of bodies in snapshot
60    #[inline]
61    #[must_use]
62    pub fn len(&self) -> usize {
63        self.bodies.len()
64    }
65
66    /// Check if empty
67    #[inline]
68    #[must_use]
69    pub fn is_empty(&self) -> bool {
70        self.bodies.is_empty()
71    }
72}
73
74/// Interpolation state manager
75///
76/// Stores two snapshots (previous and current) and provides
77/// alpha-blended positions/rotations for rendering.
78pub struct InterpolationState {
79    /// Previous physics state
80    pub prev: WorldSnapshot,
81    /// Current physics state
82    pub current: WorldSnapshot,
83}
84
85impl InterpolationState {
86    /// Create from two snapshots
87    #[must_use]
88    pub const fn new(prev: WorldSnapshot, current: WorldSnapshot) -> Self {
89        Self { prev, current }
90    }
91
92    /// Create empty
93    #[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    /// Push new physics state (current becomes prev)
102    pub fn push(&mut self, new_state: WorldSnapshot) {
103        self.prev = core::mem::replace(&mut self.current, new_state);
104    }
105
106    /// Capture and push from physics world
107    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    /// Get interpolated position for a body
113    ///
114    /// `alpha` is in [0, 1]: 0 = previous state, 1 = current state
115    #[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    /// Get interpolated rotation for a body (SLERP)
128    #[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    /// Get both interpolated position and rotation
141    #[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    /// Get all interpolated transforms
150    #[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    /// Number of bodies available for interpolation
157    #[must_use]
158    pub fn body_count(&self) -> usize {
159        self.current.bodies.len().min(self.prev.bodies.len())
160    }
161}
162
163/// Linear interpolation for `Vec3Fix`
164#[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/// Linear interpolation for Fix128
176#[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/// Spherical linear interpolation for quaternions: constant angular velocity
183/// along the shorter arc (`b` is negated when `a · b < 0`).
184///
185/// `θ = atan2(√(1 − d²), d)` with `d = a · b`, and
186/// `slerp = (sin((1 − t)θ)·a + sin(tθ)·b) / sin θ`, renormalised. Every step
187/// (`atan2`, `sin`, `sqrt`) is the crate's deterministic `Fix128` arithmetic.
188/// For nearly equal rotations (`d > 0.999`, under about 5°) the sine ratio
189/// loses precision and NLERP is used instead (its angle differs from SLERP's
190/// by under 1e-4°). Inputs are expected to be unit quaternions; two zero
191/// quaternions give the identity.
192#[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    // Ensure shortest path
197    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    // theta = acos(dot) via atan2(sqrt(1 - dot^2), dot)
209    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    // `normalize` returns the identity for a zero-length result (zero inputs)
228    result.normalize()
229}
230
231/// Normalized linear interpolation (NLERP) — faster approximate SLERP
232fn 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        // Should still be approximately identity
281        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        // Result should be between a and b
292        let len_sq =
293            result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w;
294        // Should be normalized (length ~= 1)
295        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        // Simulate
313        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        // Should be between initial and simulated position
321        assert!(pos.y < Fix128::from_int(10));
322        assert!(pos.y > Fix128::from_int(-100)); // Sanity check
323    }
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        // 端点は正確に一致
347        assert_eq!(lerp_fix128(a, b, Fix128::ZERO), a);
348        assert_eq!(lerp_fix128(a, b, Fix128::ONE), b);
349        // 2 冪の t は exact: 1/2 → 4、1/4 → 0、3/4 → 8
350        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        // a == b なら t に依らず a
360        assert_eq!(lerp_fix128(b, b, Fix128::from_ratio(3, 7)), b);
361        // 対称性: lerp(a, b, t) == lerp(b, a, 1 - t)
362        let t = Fix128::from_ratio(3, 8);
363        assert_eq!(lerp_fix128(a, b, t), lerp_fix128(b, a, Fix128::ONE - t));
364        // lerp_vec3 は成分毎の lerp_fix128
365        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        // 外挿 (t > 1) も同じ式: t = 2 → a + 2(b - a) = 28
373        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        // 重力なし、速度一定 → 1 step (dt 1/4、damping 1) で位置が exact に v/4 進む
381        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        // 1 回目: current だけ埋まり prev は空 → 補間対象 0
402        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        // step して 2 回目: 旧 current が prev へ、新 capture が current
410        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        // alpha 1/2 → 中点、alpha 0 → prev、alpha 1 → current
428        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            // 回転は両 snapshot とも identity → identity
439            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        // body を追加してもう一度 push: 補間数は prev/current の小さい方 (3)
449        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        // 範囲外 index は ZERO / IDENTITY
456        assert_eq!(
457            interp.interpolate(3, Fix128::from_ratio(1, 2)),
458            (Vec3Fix::ZERO, QuatFix::IDENTITY)
459        );
460    }
461}