1use crate::traits::Interpolate;
4
5#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
12pub struct InterpolationProgress(f32);
13
14impl InterpolationProgress {
15 #[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 #[must_use]
37 pub fn value(self) -> f32 {
38 self.0
39 }
40
41 #[must_use]
43 pub(crate) fn is_start(self) -> bool {
44 self.0 == 0.0
45 }
46
47 #[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}