Skip to main content

argui_animation/
motion.rs

1use argui_core::{Color, Point, Rect, Size, Transform2D};
2use std::fmt;
3
4pub trait MotionValue: Copy + PartialEq {
5    fn zero() -> Self;
6
7    fn add(self, other: Self) -> Self;
8
9    fn subtract(self, other: Self) -> Self;
10
11    fn scale(self, factor: f64) -> Self;
12
13    fn magnitude(self) -> f64;
14}
15
16macro_rules! scalar_motion {
17    ($type:ty) => {
18        impl MotionValue for $type {
19            fn zero() -> Self {
20                0.0
21            }
22
23            fn add(self, other: Self) -> Self {
24                self + other
25            }
26
27            fn subtract(self, other: Self) -> Self {
28                self - other
29            }
30
31            fn scale(self, factor: f64) -> Self {
32                self * factor as Self
33            }
34
35            fn magnitude(self) -> f64 {
36                self.abs() as f64
37            }
38        }
39    };
40}
41
42scalar_motion!(f32);
43scalar_motion!(f64);
44
45impl<const N: usize> MotionValue for [f32; N] {
46    fn zero() -> Self {
47        [0.0; N]
48    }
49
50    fn add(mut self, other: Self) -> Self {
51        for (value, other) in self.iter_mut().zip(other) {
52            *value += other;
53        }
54        self
55    }
56
57    fn subtract(mut self, other: Self) -> Self {
58        for (value, other) in self.iter_mut().zip(other) {
59            *value -= other;
60        }
61        self
62    }
63
64    fn scale(mut self, factor: f64) -> Self {
65        for value in &mut self {
66            *value *= factor as f32;
67        }
68        self
69    }
70
71    fn magnitude(self) -> f64 {
72        self.into_iter()
73            .map(|value| f64::from(value).powi(2))
74            .sum::<f64>()
75            .sqrt()
76    }
77}
78
79impl MotionValue for Point {
80    fn zero() -> Self {
81        Self::default()
82    }
83
84    fn add(self, other: Self) -> Self {
85        Self::new(self.x + other.x, self.y + other.y)
86    }
87
88    fn subtract(self, other: Self) -> Self {
89        Self::new(self.x - other.x, self.y - other.y)
90    }
91
92    fn scale(self, factor: f64) -> Self {
93        Self::new(self.x * factor as f32, self.y * factor as f32)
94    }
95
96    fn magnitude(self) -> f64 {
97        f64::from(self.x).hypot(f64::from(self.y))
98    }
99}
100
101impl MotionValue for Size {
102    fn zero() -> Self {
103        Self::default()
104    }
105
106    fn add(self, other: Self) -> Self {
107        Self::new(self.width + other.width, self.height + other.height)
108    }
109
110    fn subtract(self, other: Self) -> Self {
111        Self::new(self.width - other.width, self.height - other.height)
112    }
113
114    fn scale(self, factor: f64) -> Self {
115        Self::new(self.width * factor as f32, self.height * factor as f32)
116    }
117
118    fn magnitude(self) -> f64 {
119        f64::from(self.width).hypot(f64::from(self.height))
120    }
121}
122
123impl MotionValue for Rect {
124    fn zero() -> Self {
125        Self::default()
126    }
127
128    fn add(self, other: Self) -> Self {
129        Self::new(self.origin.add(other.origin), self.size.add(other.size))
130    }
131
132    fn subtract(self, other: Self) -> Self {
133        Self::new(
134            self.origin.subtract(other.origin),
135            self.size.subtract(other.size),
136        )
137    }
138
139    fn scale(self, factor: f64) -> Self {
140        Self::new(self.origin.scale(factor), self.size.scale(factor))
141    }
142
143    fn magnitude(self) -> f64 {
144        self.origin.magnitude().hypot(self.size.magnitude())
145    }
146}
147
148impl MotionValue for Color {
149    fn zero() -> Self {
150        Self::TRANSPARENT
151    }
152
153    fn add(self, other: Self) -> Self {
154        map_color(self, other, |left, right| left + right)
155    }
156
157    fn subtract(self, other: Self) -> Self {
158        map_color(self, other, |left, right| left - right)
159    }
160
161    fn scale(self, factor: f64) -> Self {
162        let [red, green, blue, alpha] = self.to_linear_rgba();
163        let factor = factor as f32;
164        Self::linear_rgba(red * factor, green * factor, blue * factor, alpha * factor)
165    }
166
167    fn magnitude(self) -> f64 {
168        self.to_linear_rgba()
169            .into_iter()
170            .map(|channel| f64::from(channel).powi(2))
171            .sum::<f64>()
172            .sqrt()
173    }
174}
175
176impl MotionValue for Transform2D {
177    fn zero() -> Self {
178        Self {
179            translation: Point::default(),
180            scale: Point::default(),
181            rotation: 0.0,
182            skew: Point::default(),
183        }
184    }
185
186    fn add(self, other: Self) -> Self {
187        Self {
188            translation: self.translation.add(other.translation),
189            scale: self.scale.add(other.scale),
190            rotation: self.rotation + other.rotation,
191            skew: self.skew.add(other.skew),
192        }
193    }
194
195    fn subtract(self, other: Self) -> Self {
196        Self {
197            translation: self.translation.subtract(other.translation),
198            scale: self.scale.subtract(other.scale),
199            rotation: self.rotation - other.rotation,
200            skew: self.skew.subtract(other.skew),
201        }
202    }
203
204    fn scale(self, factor: f64) -> Self {
205        Self {
206            translation: self.translation.scale(factor),
207            scale: self.scale.scale(factor),
208            rotation: self.rotation * factor as f32,
209            skew: self.skew.scale(factor),
210        }
211    }
212
213    fn magnitude(self) -> f64 {
214        self.translation
215            .magnitude()
216            .hypot(self.scale.magnitude())
217            .hypot(f64::from(self.rotation))
218            .hypot(self.skew.magnitude())
219    }
220}
221
222fn map_color(left: Color, right: Color, operation: impl Fn(f32, f32) -> f32) -> Color {
223    let left = left.to_linear_rgba();
224    let right = right.to_linear_rgba();
225    Color::linear_rgba(
226        operation(left[0], right[0]),
227        operation(left[1], right[1]),
228        operation(left[2], right[2]),
229        operation(left[3], right[3]),
230    )
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub enum PhysicsError {
235    InvalidMass,
236    InvalidStiffness,
237    InvalidDamping,
238    InvalidRestThreshold,
239    InvalidDecay,
240    InvalidBounds,
241}
242
243impl fmt::Display for PhysicsError {
244    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
245        match self {
246            Self::InvalidMass => formatter.write_str("spring mass must be finite and positive"),
247            Self::InvalidStiffness => {
248                formatter.write_str("spring stiffness must be finite and positive")
249            }
250            Self::InvalidDamping => {
251                formatter.write_str("spring damping must be finite and non-negative")
252            }
253            Self::InvalidRestThreshold => {
254                formatter.write_str("rest thresholds must be finite and non-negative")
255            }
256            Self::InvalidDecay => formatter.write_str("decay rate must be finite and positive"),
257            Self::InvalidBounds => formatter.write_str("inertia bounds must be finite and ordered"),
258        }
259    }
260}
261
262impl std::error::Error for PhysicsError {}