godot_core/builtin/math/
float.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use 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
18/// Trait that provides Godot math functions as extensions on `f32` and `f64`.
19pub trait FloatExt: private::Sealed + Copy {
20    const CMP_EPSILON: Self;
21
22    /// Linearly interpolates from `self` to `to` by `weight`.
23    ///
24    /// `weight` should be in the range `0.0 ..= 1.0`, but values outside this are allowed and will perform
25    /// linear extrapolation.
26    fn lerp(self, to: Self, weight: Self) -> Self;
27
28    /// Check if two angles are approximately equal, by comparing the distance
29    /// between the points on the unit circle with 0 using [`real::approx_eq`].
30    fn is_angle_equal_approx(self, other: Self) -> bool;
31
32    /// Check if `self` is within [`Self::CMP_EPSILON`] of `0.0`.
33    fn is_zero_approx(self) -> bool;
34
35    /// Returns the floating-point modulus of `self` divided by `pmod`, wrapping equally in positive and negative.
36    fn fposmod(self, pmod: Self) -> Self;
37
38    /// Returns the multiple of `step` that is closest to `self`.
39    fn snapped(self, step: Self) -> Self;
40
41    /// Godot's `sign` function, returns `0.0` when self is `0.0`.
42    ///
43    /// See also [`f32::signum`] and [`f64::signum`], which always return `-1.0` or `1.0` (or `NaN`).
44    fn sign(self) -> Self;
45
46    /// Returns the derivative at the given `t` on a one-dimensional Bézier curve defined by the given
47    /// `control_1`, `control_2`, and `end` points.
48    fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: Self) -> Self;
49
50    /// Returns the point at the given `t` on a one-dimensional Bézier curve defined by the given
51    /// `control_1`, `control_2`, and `end` points.
52    fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: Self) -> Self;
53
54    /// Cubic interpolates between two values by the factor defined in `weight` with `pre` and `post` values.
55    fn cubic_interpolate(self, to: Self, pre: Self, post: Self, weight: Self) -> Self;
56
57    /// Cubic interpolates between two values by the factor defined in `weight` with `pre` and `post` values.
58    /// It can perform smoother interpolation than [`cubic_interpolate`](FloatExt::cubic_interpolate) by the time values.
59    #[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    /// Linearly interpolates between two angles (in radians) by a `weight` value
72    /// between 0.0 and 1.0.
73    ///
74    /// Similar to [`lerp`][Self::lerp], but interpolates correctly when the angles wrap around
75    /// [`TAU`][crate::builtin::real_consts::TAU].
76    ///
77    /// The resulting angle is not normalized.
78    ///
79    /// Note: This function lerps through the shortest path between `from` and
80    /// `to`. However, when these two angles are approximately `PI + k * TAU` apart
81    /// for any integer `k`, it's not obvious which way they lerp due to
82    /// floating-point precision errors. For example, with single-precision floats
83    /// `lerp_angle(0.0, PI, weight)` lerps clockwise, while `lerp_angle(0.0, PI + 3.0 * TAU, weight)`
84    /// lerps counter-clockwise.
85    ///
86    /// _Godot equivalent: @GlobalScope.lerp_angle()_
87    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                    // `self` is `NaN`
136                    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    // Create functions that take references for use in `assert_eq/ne_approx`.
259    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    // As mentioned in the docs for `lerp_angle`, direction can be unpredictable
296    // when lerping towards PI radians, this also means it's different for single vs
297    // double precision floats.
298
299    #[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}