Skip to main content

armas_basic/animation/
interpolate.rs

1//! Interpolation trait and helpers for animating values.
2//!
3//! Implemented for `f32`, `f64`, `Color32`, `Pos2`, `Vec2`, and `Rect`.
4
5use egui::{Color32, Pos2, Rect, Vec2};
6
7/// Trait for types that can be interpolated between two values
8///
9/// # Example
10///
11/// ```rust
12/// use armas_basic::animation::Interpolate;
13///
14/// let a = 0.0_f32;
15/// let b = 10.0_f32;
16/// assert_eq!(a.interpolate(&b, 0.5), 5.0);
17/// ```
18pub trait Interpolate: Clone {
19    /// Interpolate between self and other by factor t (0.0 to 1.0)
20    #[must_use]
21    fn interpolate(&self, other: &Self, t: f32) -> Self;
22}
23
24// Implementation for f32
25impl Interpolate for f32 {
26    fn interpolate(&self, other: &Self, t: f32) -> Self {
27        self + (other - self) * t
28    }
29}
30
31// Implementation for f64
32impl Interpolate for f64 {
33    fn interpolate(&self, other: &Self, t: f32) -> Self {
34        self + (other - self) * Self::from(t)
35    }
36}
37
38// Implementation for Vec2
39impl Interpolate for Vec2 {
40    fn interpolate(&self, other: &Self, t: f32) -> Self {
41        Self::new(
42            self.x.interpolate(&other.x, t),
43            self.y.interpolate(&other.y, t),
44        )
45    }
46}
47
48// Implementation for Pos2
49impl Interpolate for Pos2 {
50    fn interpolate(&self, other: &Self, t: f32) -> Self {
51        Self::new(
52            self.x.interpolate(&other.x, t),
53            self.y.interpolate(&other.y, t),
54        )
55    }
56}
57
58// Implementation for Rect
59impl Interpolate for Rect {
60    fn interpolate(&self, other: &Self, t: f32) -> Self {
61        Self {
62            min: self.min.interpolate(&other.min, t),
63            max: self.max.interpolate(&other.max, t),
64        }
65    }
66}
67
68// Implementation for Color32
69impl Interpolate for Color32 {
70    #[allow(clippy::many_single_char_names)]
71    fn interpolate(&self, other: &Self, t: f32) -> Self {
72        let r = interpolate_u8(self.r(), other.r(), t);
73        let g = interpolate_u8(self.g(), other.g(), t);
74        let b = interpolate_u8(self.b(), other.b(), t);
75        let a = interpolate_u8(self.a(), other.a(), t);
76        Self::from_rgba_premultiplied(r, g, b, a)
77    }
78}
79
80// Helper function to interpolate u8 values
81fn interpolate_u8(a: u8, b: u8, t: f32) -> u8 {
82    let a = f32::from(a);
83    let b = f32::from(b);
84    (a + (b - a) * t).round().clamp(0.0, 255.0) as u8
85}
86
87// Implementation for tuples (useful for multiple values)
88impl<A: Interpolate, B: Interpolate> Interpolate for (A, B) {
89    fn interpolate(&self, other: &Self, t: f32) -> Self {
90        (
91            self.0.interpolate(&other.0, t),
92            self.1.interpolate(&other.1, t),
93        )
94    }
95}
96
97impl<A: Interpolate, B: Interpolate, C: Interpolate> Interpolate for (A, B, C) {
98    fn interpolate(&self, other: &Self, t: f32) -> Self {
99        (
100            self.0.interpolate(&other.0, t),
101            self.1.interpolate(&other.1, t),
102            self.2.interpolate(&other.2, t),
103        )
104    }
105}
106
107// Implementation for Option (interpolate if both are Some)
108impl<T: Interpolate> Interpolate for Option<T> {
109    fn interpolate(&self, other: &Self, t: f32) -> Self {
110        match (self, other) {
111            (Some(a), Some(b)) => Some(a.interpolate(b, t)),
112            (Some(_), None) if t < 0.5 => self.clone(),
113            (None, Some(_)) if t >= 0.5 => other.clone(),
114            _ => None,
115        }
116    }
117}
118
119/// Helper function for smooth interpolation with custom easing
120pub fn lerp<T: Interpolate>(a: &T, b: &T, t: f32) -> T {
121    a.interpolate(b, t)
122}
123
124/// Smooth step interpolation (S-curve)
125#[must_use]
126pub fn smooth_step(t: f32) -> f32 {
127    let t = t.clamp(0.0, 1.0);
128    t * t * (3.0 - 2.0 * t)
129}
130
131/// Smoother step interpolation (smoother S-curve)
132#[must_use]
133pub fn smoother_step(t: f32) -> f32 {
134    let t = t.clamp(0.0, 1.0);
135    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_f32_interpolate() {
144        assert_eq!(0.0f32.interpolate(&10.0, 0.0), 0.0);
145        assert_eq!(0.0f32.interpolate(&10.0, 0.5), 5.0);
146        assert_eq!(0.0f32.interpolate(&10.0, 1.0), 10.0);
147    }
148
149    #[test]
150    fn test_vec2_interpolate() {
151        let a = Vec2::new(0.0, 0.0);
152        let b = Vec2::new(10.0, 20.0);
153
154        let mid = a.interpolate(&b, 0.5);
155        assert_eq!(mid.x, 5.0);
156        assert_eq!(mid.y, 10.0);
157    }
158
159    #[test]
160    fn test_color_interpolate() {
161        let black = Color32::BLACK;
162        let white = Color32::WHITE;
163
164        let gray = black.interpolate(&white, 0.5);
165        assert!(gray.r() > 100 && gray.r() < 155);
166    }
167
168    #[test]
169    fn test_smooth_step() {
170        assert_eq!(smooth_step(0.0), 0.0);
171        assert_eq!(smooth_step(1.0), 1.0);
172        assert!(smooth_step(0.5) > 0.4 && smooth_step(0.5) < 0.6);
173    }
174
175    #[test]
176    fn test_tuple_interpolate() {
177        let a = (0.0f32, 0.0f32);
178        let b = (10.0f32, 20.0f32);
179
180        let mid = a.interpolate(&b, 0.5);
181        assert_eq!(mid, (5.0, 10.0));
182    }
183}