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 {}