Skip to main content

aura_anim_core/
interpolate.rs

1//! Interpolation progress and built-in interpolation implementations.
2
3use crate::traits::Interpolate;
4
5/// A normalized interpolation progress value.
6///
7/// Rules:
8/// - `NaN` becomes `0.0`.
9/// - Values below `0.0` become `0.0`.
10/// - Values above `1.0` become `1.0`.
11#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
12pub struct InterpolationProgress(f32);
13
14impl InterpolationProgress {
15    /// Creates a new `InterpolationProgress` with the given progress value.
16    ///
17    /// `NaN` values are replaced with `0.0`, and values are clamped to `[0.0, 1.0]`.
18    #[must_use]
19    pub fn new(progress: f32) -> Self {
20        if progress.is_nan() {
21            Self(0.0)
22        } else {
23            Self(progress.clamp(0.0, 1.0))
24        }
25    }
26
27    pub(crate) fn extrapolated(progress: f32) -> Self {
28        if progress.is_finite() {
29            Self(progress)
30        } else {
31            Self(0.0)
32        }
33    }
34
35    /// Returns the raw progress value.
36    #[must_use]
37    pub fn value(self) -> f32 {
38        self.0
39    }
40
41    /// Returns `true` if the progress is at the start (0.0).
42    #[must_use]
43    pub(crate) fn is_start(self) -> bool {
44        self.0 == 0.0
45    }
46
47    /// Returns `true` if the progress is at the end (1.0).
48    #[must_use]
49    pub(crate) fn is_end(self) -> bool {
50        (self.0 - 1.0).abs() < 1e-5
51    }
52}
53
54impl From<f32> for InterpolationProgress {
55    fn from(progress: f32) -> Self {
56        Self::new(progress)
57    }
58}
59
60impl Interpolate for f32 {
61    fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
62        interpolate_with_progress(from, to, progress, |from, to, progress| {
63            from + (to - from) * progress.value()
64        })
65    }
66}
67
68impl Interpolate for f64 {
69    fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
70        interpolate_with_progress(from, to, progress, |from, to, progress| {
71            let progress = f64::from(progress.value());
72            from + (to - from) * progress
73        })
74    }
75}
76
77macro_rules! impl_interpolate_integer {
78    ($($ty:ty),+ $(,)?) => {
79        $(
80            impl Interpolate for $ty {
81                fn interpolate_progress(
82                    from: &Self,
83                    to: &Self,
84                    progress: InterpolationProgress,
85                ) -> Self {
86                    interpolate_with_progress(from, to, progress, |from, to, progress| {
87                        let from = f64::from(*from);
88                        let to = f64::from(*to);
89                        let progress = f64::from(progress.value());
90
91                        #[allow(
92                            clippy::cast_possible_truncation,
93                            reason= "The interpolated value is rounded back to the integer type. Endpoints are returned before this branch.")]
94                        #[allow(
95                            clippy::cast_sign_loss,
96                            reason= "The interpolated value is rounded back to the integer type. Endpoints are returned before this branch.")]
97                        {
98                            (from + (to - from) * progress).round() as Self
99                        }
100                    })
101                }
102            }
103        )+
104    };
105}
106
107impl_interpolate_integer!(u8, i8, u16, i16, u32, i32);
108
109#[inline]
110fn interpolate_with_progress<T: Clone>(
111    from: &T,
112    to: &T,
113    progress: InterpolationProgress,
114    interpolate_between: impl FnOnce(&T, &T, InterpolationProgress) -> T,
115) -> T {
116    if progress.is_start() {
117        from.clone()
118    } else if progress.is_end() {
119        to.clone()
120    } else {
121        interpolate_between(from, to, progress)
122    }
123}
124
125macro_rules! impl_interpolate_tuple {
126    ($($name:ident : $index:tt),+) => {
127        impl<$($name),+> Interpolate for ($($name,)+)
128        where
129            $($name: Interpolate + Clone),+
130        {
131            fn interpolate_progress(
132                from: &Self,
133                to: &Self,
134                progress: InterpolationProgress,
135            ) -> Self {
136                interpolate_with_progress(from, to, progress, |from, to, progress| {
137                    (
138                        $(
139                            $name::interpolate_progress(&from.$index, &to.$index, progress),
140                        )+
141                    )
142                })
143            }
144        }
145    };
146}
147
148impl_interpolate_tuple!(A: 0, B: 1);
149impl_interpolate_tuple!(A: 0, B: 1, C: 2);
150impl_interpolate_tuple!(A: 0, B: 1, C: 2, D: 3);
151
152impl<T, const N: usize> Interpolate for [T; N]
153where
154    T: Interpolate + Clone,
155{
156    fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
157        interpolate_with_progress(from, to, progress, |from, to, progress| {
158            std::array::from_fn(|i| {
159                <T as Interpolate>::interpolate_progress(&from[i], &to[i], progress)
160            })
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::InterpolationProgress;
168    use crate::Interpolate;
169    use float_cmp::assert_approx_eq;
170
171    #[test]
172    fn progress_clamps_invalid_values() {
173        assert_approx_eq!(f32, InterpolationProgress::new(f32::NAN).value(), 0.0);
174        assert_approx_eq!(f32, InterpolationProgress::new(-0.5).value(), 0.0);
175        assert_approx_eq!(f32, InterpolationProgress::new(1.5).value(), 1.0);
176    }
177
178    #[test]
179    fn progress_allows_finite_extrapolation() {
180        assert_approx_eq!(f32, InterpolationProgress::extrapolated(1.5).value(), 1.5);
181        assert_approx_eq!(
182            f32,
183            InterpolationProgress::extrapolated(f32::INFINITY).value(),
184            0.0
185        );
186    }
187
188    #[test]
189    fn interpolates_floating_point_values() {
190        assert_approx_eq!(f32, f32::interpolate(&2.0, &6.0, 0.25), 3.0);
191        assert_approx_eq!(f64, f64::interpolate(&2.0, &6.0, 0.25), 3.0);
192    }
193
194    #[test]
195    fn interpolates_integer_values_with_rounding() {
196        assert_eq!(i32::interpolate(&0, &10, 0.26), 3);
197        assert_eq!(u8::interpolate(&0, &10, 0.24), 2);
198    }
199
200    #[test]
201    fn interpolates_tuples_and_arrays() {
202        assert_eq!(
203            <(f32, i32)>::interpolate(&(0.0, 0), &(10.0, 10), 0.5),
204            (5.0, 5)
205        );
206        let array = <[f32; 2]>::interpolate(&[0.0, 10.0], &[10.0, 20.0], 0.5);
207
208        assert_approx_eq!(f32, array[0], 5.0);
209        assert_approx_eq!(f32, array[1], 15.0);
210    }
211}