1use super::ApproxEq;
9use crate::builtin::{real, RealConv, Vector2};
10
11mod private {
12 pub trait Sealed {}
13
14 impl Sealed for f32 {}
15 impl Sealed for f64 {}
16}
17
18pub trait FloatExt: private::Sealed + Copy {
20 const CMP_EPSILON: Self;
21
22 fn lerp(self, to: Self, weight: Self) -> Self;
27
28 fn is_angle_equal_approx(self, other: Self) -> bool;
31
32 fn is_zero_approx(self) -> bool;
34
35 fn fposmod(self, pmod: Self) -> Self;
37
38 fn snapped(self, step: Self) -> Self;
40
41 fn sign(self) -> Self;
45
46 fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: Self) -> Self;
49
50 fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: Self) -> Self;
53
54 fn cubic_interpolate(self, to: Self, pre: Self, post: Self, weight: Self) -> Self;
56
57 #[allow(clippy::too_many_arguments)]
60 fn cubic_interpolate_in_time(
61 self,
62 to: Self,
63 pre: Self,
64 post: Self,
65 weight: Self,
66 to_t: Self,
67 pre_t: Self,
68 post_t: Self,
69 ) -> Self;
70
71 fn lerp_angle(self, to: Self, weight: Self) -> Self;
88}
89
90macro_rules! impl_float_ext {
91 ($Ty:ty, $consts:path, $to_real:ident) => {
92 impl FloatExt for $Ty {
93 const CMP_EPSILON: Self = 0.00001;
94
95 fn lerp(self, to: Self, t: Self) -> Self {
96 self + ((to - self) * t)
97 }
98
99 fn is_angle_equal_approx(self, other: Self) -> bool {
100 let (x1, y1) = self.sin_cos();
101 let (x2, y2) = other.sin_cos();
102
103 let point_1 = Vector2::new(real::$to_real(x1), real::$to_real(y1));
104 let point_2 = Vector2::new(real::$to_real(x2), real::$to_real(y2));
105
106 point_1.distance_to(point_2).is_zero_approx()
107 }
108
109 fn is_zero_approx(self) -> bool {
110 self.abs() < Self::CMP_EPSILON
111 }
112
113 fn fposmod(self, pmod: Self) -> Self {
114 let mut value = self % pmod;
115 if (value < 0.0 && pmod > 0.0) || (value > 0.0 && pmod < 0.0) {
116 value += pmod;
117 }
118 value
119 }
120
121 fn snapped(mut self, step: Self) -> Self {
122 if step != 0.0 {
123 self = (self / step + 0.5).floor() * step
124 }
125 self
126 }
127
128 fn sign(self) -> Self {
129 use std::cmp::Ordering;
130
131 match self.partial_cmp(&0.0) {
132 Some(Ordering::Equal) => 0.0,
133 Some(Ordering::Greater) => 1.0,
134 Some(Ordering::Less) => -1.0,
135 None => Self::NAN,
137 }
138 }
139
140 fn bezier_derivative(
141 self,
142 control_1: Self,
143 control_2: Self,
144 end: Self,
145 t: Self,
146 ) -> Self {
147 let omt = 1.0 - t;
148 let omt2 = omt * omt;
149 let t2 = t * t;
150 (control_1 - self) * 3.0 * omt2
151 + (control_2 - control_1) * 6.0 * omt * t
152 + (end - control_2) * 3.0 * t2
153 }
154
155 fn bezier_interpolate(
156 self,
157 control_1: Self,
158 control_2: Self,
159 end: Self,
160 t: Self,
161 ) -> Self {
162 let omt = 1.0 - t;
163 let omt2 = omt * omt;
164 let omt3 = omt2 * omt;
165 let t2 = t * t;
166 let t3 = t2 * t;
167 self * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3
168 }
169
170 fn cubic_interpolate(self, to: Self, pre: Self, post: Self, weight: Self) -> Self {
171 0.5 * ((self * 2.0)
172 + (-pre + to) * weight
173 + (2.0 * pre - 5.0 * self + 4.0 * to - post) * (weight * weight)
174 + (-pre + 3.0 * self - 3.0 * to + post) * (weight * weight * weight))
175 }
176
177 fn cubic_interpolate_in_time(
178 self,
179 to: Self,
180 pre: Self,
181 post: Self,
182 weight: Self,
183 to_t: Self,
184 pre_t: Self,
185 post_t: Self,
186 ) -> Self {
187 let t = Self::lerp(0.0, to_t, weight);
188
189 let a1 = Self::lerp(
190 pre,
191 self,
192 if pre_t == 0.0 {
193 0.0
194 } else {
195 (t - pre_t) / -pre_t
196 },
197 );
198
199 let a2 = Self::lerp(self, to, if to_t == 0.0 { 0.5 } else { t / to_t });
200
201 let a3 = Self::lerp(
202 to,
203 post,
204 if post_t - to_t == 0.0 {
205 1.0
206 } else {
207 (t - to_t) / (post_t - to_t)
208 },
209 );
210
211 let b1 = Self::lerp(
212 a1,
213 a2,
214 if to_t - pre_t == 0.0 {
215 0.0
216 } else {
217 (t - pre_t) / (to_t - pre_t)
218 },
219 );
220
221 let b2 = Self::lerp(a2, a3, if post_t == 0.0 { 1.0 } else { t / post_t });
222
223 Self::lerp(b1, b2, if to_t == 0.0 { 0.5 } else { t / to_t })
224 }
225
226 fn lerp_angle(self, to: Self, weight: Self) -> Self {
227 use $consts;
228
229 let difference = (to - self) % consts::TAU;
230 let distance = (2.0 * difference) % consts::TAU - difference;
231 self + distance * weight
232 }
233 }
234
235 impl ApproxEq for $Ty {
236 fn approx_eq(&self, other: &Self) -> bool {
237 if self == other {
238 return true;
239 }
240 let mut tolerance = Self::CMP_EPSILON * self.abs();
241 if tolerance < Self::CMP_EPSILON {
242 tolerance = Self::CMP_EPSILON;
243 }
244 (self - other).abs() < tolerance
245 }
246 }
247 };
248}
249
250impl_float_ext!(f32, std::f32::consts, from_f32);
251impl_float_ext!(f64, std::f64::consts, from_f64);
252
253#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
254mod test {
255 use super::*;
256 use crate::assert_eq_approx;
257
258 fn is_angle_equal_approx_f32(a: &f32, b: &f32) -> bool {
260 a.is_angle_equal_approx(*b)
261 }
262
263 fn is_angle_equal_approx_f64(a: &f64, b: &f64) -> bool {
264 a.is_angle_equal_approx(*b)
265 }
266
267 #[test]
268 fn angle_equal_approx_f32() {
269 use std::f32::consts::{PI, TAU};
270
271 assert_eq_approx!(1.0, 1.000001, fn = is_angle_equal_approx_f32);
272 assert_eq_approx!(0.0, TAU, fn = is_angle_equal_approx_f32);
273 assert_eq_approx!(PI, -PI, fn = is_angle_equal_approx_f32);
274 assert_eq_approx!(4.45783, -(TAU - 4.45783), fn = is_angle_equal_approx_f32);
275 assert_eq_approx!(31.0 * PI, -13.0 * PI, fn = is_angle_equal_approx_f32);
276 }
277
278 #[test]
279 fn angle_equal_approx_f64() {
280 use std::f64::consts::{PI, TAU};
281
282 assert_eq_approx!(1.0, 1.000001, fn = is_angle_equal_approx_f64);
283 assert_eq_approx!(0.0, TAU, fn = is_angle_equal_approx_f64);
284 assert_eq_approx!(PI, -PI, fn = is_angle_equal_approx_f64);
285 assert_eq_approx!(4.45783, -(TAU - 4.45783), fn = is_angle_equal_approx_f64);
286 assert_eq_approx!(31.0 * PI, -13.0 * PI, fn = is_angle_equal_approx_f64);
287 }
288
289 #[test]
290 #[should_panic(expected = "I am inside format")]
291 fn eq_approx_fail_with_message() {
292 assert_eq_approx!(1.0, 2.0, "I am inside {}", "format");
293 }
294
295 #[test]
300 fn lerp_angle_test_f32() {
301 use std::f32::consts::{FRAC_PI_2, PI, TAU};
302
303 assert_eq_approx!(f32::lerp_angle(0.0, PI, 0.5), -FRAC_PI_2, fn = is_angle_equal_approx_f32);
304
305 assert_eq_approx!(
306 f32::lerp_angle(0.0, PI + 3.0 * TAU, 0.5),
307 FRAC_PI_2,
308 fn = is_angle_equal_approx_f32
309 );
310
311 let angle = PI * 2.0 / 3.0;
312 assert_eq_approx!(
313 f32::lerp_angle(-5.0 * TAU, angle + 3.0 * TAU, 0.5),
314 (angle / 2.0),
315 fn = is_angle_equal_approx_f32
316 );
317 }
318
319 #[test]
320 fn lerp_angle_test_f64() {
321 use std::f64::consts::{FRAC_PI_2, PI, TAU};
322
323 assert_eq_approx!(f64::lerp_angle(0.0, PI, 0.5), -FRAC_PI_2, fn = is_angle_equal_approx_f64);
324
325 assert_eq_approx!(
326 f64::lerp_angle(0.0, PI + 3.0 * TAU, 0.5),
327 -FRAC_PI_2,
328 fn = is_angle_equal_approx_f64
329 );
330
331 let angle = PI * 2.0 / 3.0;
332 assert_eq_approx!(
333 f64::lerp_angle(-5.0 * TAU, angle + 3.0 * TAU, 0.5),
334 (angle / 2.0),
335 fn = is_angle_equal_approx_f64
336 );
337 }
338}