bevy_math/
rotation2d.rs

1use glam::FloatExt;
2
3use crate::prelude::{Mat2, Vec2};
4
5#[cfg(feature = "bevy_reflect")]
6use bevy_reflect::{std_traits::ReflectDefault, Reflect};
7#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
8use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
9
10/// A counterclockwise 2D rotation.
11///
12/// # Example
13///
14/// ```
15/// # use approx::assert_relative_eq;
16/// # use bevy_math::{Rot2, Vec2};
17/// use std::f32::consts::PI;
18///
19/// // Create rotations from radians or degrees
20/// let rotation1 = Rot2::radians(PI / 2.0);
21/// let rotation2 = Rot2::degrees(45.0);
22///
23/// // Get the angle back as radians or degrees
24/// assert_eq!(rotation1.as_degrees(), 90.0);
25/// assert_eq!(rotation2.as_radians(), PI / 4.0);
26///
27/// // "Add" rotations together using `*`
28/// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0));
29///
30/// // Rotate vectors
31/// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y);
32/// ```
33#[derive(Clone, Copy, Debug, PartialEq)]
34#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
35#[cfg_attr(
36    feature = "bevy_reflect",
37    derive(Reflect),
38    reflect(Debug, PartialEq, Default)
39)]
40#[cfg_attr(
41    all(feature = "serialize", feature = "bevy_reflect"),
42    reflect(Serialize, Deserialize)
43)]
44#[doc(alias = "rotation", alias = "rotation2d", alias = "rotation_2d")]
45pub struct Rot2 {
46    /// The cosine of the rotation angle in radians.
47    ///
48    /// This is the real part of the unit complex number representing the rotation.
49    pub cos: f32,
50    /// The sine of the rotation angle in radians.
51    ///
52    /// This is the imaginary part of the unit complex number representing the rotation.
53    pub sin: f32,
54}
55
56impl Default for Rot2 {
57    fn default() -> Self {
58        Self::IDENTITY
59    }
60}
61
62impl Rot2 {
63    /// No rotation.
64    pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
65
66    /// A rotation of π radians.
67    pub const PI: Self = Self {
68        cos: -1.0,
69        sin: 0.0,
70    };
71
72    /// A counterclockwise rotation of π/2 radians.
73    pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
74
75    /// A counterclockwise rotation of π/3 radians.
76    pub const FRAC_PI_3: Self = Self {
77        cos: 0.5,
78        sin: 0.866_025_4,
79    };
80
81    /// A counterclockwise rotation of π/4 radians.
82    pub const FRAC_PI_4: Self = Self {
83        cos: core::f32::consts::FRAC_1_SQRT_2,
84        sin: core::f32::consts::FRAC_1_SQRT_2,
85    };
86
87    /// A counterclockwise rotation of π/6 radians.
88    pub const FRAC_PI_6: Self = Self {
89        cos: 0.866_025_4,
90        sin: 0.5,
91    };
92
93    /// A counterclockwise rotation of π/8 radians.
94    pub const FRAC_PI_8: Self = Self {
95        cos: 0.923_879_5,
96        sin: 0.382_683_43,
97    };
98
99    /// Creates a [`Rot2`] from a counterclockwise angle in radians.
100    #[inline]
101    pub fn radians(radians: f32) -> Self {
102        #[cfg(feature = "libm")]
103        let (sin, cos) = (
104            libm::sin(radians as f64) as f32,
105            libm::cos(radians as f64) as f32,
106        );
107        #[cfg(not(feature = "libm"))]
108        let (sin, cos) = radians.sin_cos();
109
110        Self::from_sin_cos(sin, cos)
111    }
112
113    /// Creates a [`Rot2`] from a counterclockwise angle in degrees.
114    #[inline]
115    pub fn degrees(degrees: f32) -> Self {
116        Self::radians(degrees.to_radians())
117    }
118
119    /// Creates a [`Rot2`] from the sine and cosine of an angle in radians.
120    ///
121    /// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
122    ///
123    /// # Panics
124    ///
125    /// Panics if `sin * sin + cos * cos != 1.0` when the `glam_assert` feature is enabled.
126    #[inline]
127    pub fn from_sin_cos(sin: f32, cos: f32) -> Self {
128        let rotation = Self { sin, cos };
129        debug_assert!(
130            rotation.is_normalized(),
131            "the given sine and cosine produce an invalid rotation"
132        );
133        rotation
134    }
135
136    /// Returns the rotation in radians in the `(-pi, pi]` range.
137    #[inline]
138    pub fn as_radians(self) -> f32 {
139        #[cfg(feature = "libm")]
140        {
141            libm::atan2(self.sin as f64, self.cos as f64) as f32
142        }
143        #[cfg(not(feature = "libm"))]
144        {
145            f32::atan2(self.sin, self.cos)
146        }
147    }
148
149    /// Returns the rotation in degrees in the `(-180, 180]` range.
150    #[inline]
151    pub fn as_degrees(self) -> f32 {
152        self.as_radians().to_degrees()
153    }
154
155    /// Returns the sine and cosine of the rotation angle in radians.
156    #[inline]
157    pub const fn sin_cos(self) -> (f32, f32) {
158        (self.sin, self.cos)
159    }
160
161    /// Computes the length or norm of the complex number used to represent the rotation.
162    ///
163    /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
164    /// can be a result of incorrect construction or floating point error caused by
165    /// successive operations.
166    #[inline]
167    #[doc(alias = "norm")]
168    pub fn length(self) -> f32 {
169        Vec2::new(self.sin, self.cos).length()
170    }
171
172    /// Computes the squared length or norm of the complex number used to represent the rotation.
173    ///
174    /// This is generally faster than [`Rot2::length()`], as it avoids a square
175    /// root operation.
176    ///
177    /// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
178    /// can be a result of incorrect construction or floating point error caused by
179    /// successive operations.
180    #[inline]
181    #[doc(alias = "norm2")]
182    pub fn length_squared(self) -> f32 {
183        Vec2::new(self.sin, self.cos).length_squared()
184    }
185
186    /// Computes `1.0 / self.length()`.
187    ///
188    /// For valid results, `self` must _not_ have a length of zero.
189    #[inline]
190    pub fn length_recip(self) -> f32 {
191        Vec2::new(self.sin, self.cos).length_recip()
192    }
193
194    /// Returns `self` with a length of `1.0` if possible, and `None` otherwise.
195    ///
196    /// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),
197    /// or if either of them is NaN or infinite.
198    ///
199    /// Note that [`Rot2`] should typically already be normalized by design.
200    /// Manual normalization is only needed when successive operations result in
201    /// accumulated floating point error, or if the rotation was constructed
202    /// with invalid values.
203    #[inline]
204    pub fn try_normalize(self) -> Option<Self> {
205        let recip = self.length_recip();
206        if recip.is_finite() && recip > 0.0 {
207            Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))
208        } else {
209            None
210        }
211    }
212
213    /// Returns `self` with a length of `1.0`.
214    ///
215    /// Note that [`Rot2`] should typically already be normalized by design.
216    /// Manual normalization is only needed when successive operations result in
217    /// accumulated floating point error, or if the rotation was constructed
218    /// with invalid values.
219    ///
220    /// # Panics
221    ///
222    /// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.
223    #[inline]
224    pub fn normalize(self) -> Self {
225        let length_recip = self.length_recip();
226        Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)
227    }
228
229    /// Returns `true` if the rotation is neither infinite nor NaN.
230    #[inline]
231    pub fn is_finite(self) -> bool {
232        self.sin.is_finite() && self.cos.is_finite()
233    }
234
235    /// Returns `true` if the rotation is NaN.
236    #[inline]
237    pub fn is_nan(self) -> bool {
238        self.sin.is_nan() || self.cos.is_nan()
239    }
240
241    /// Returns whether `self` has a length of `1.0` or not.
242    ///
243    /// Uses a precision threshold of approximately `1e-4`.
244    #[inline]
245    pub fn is_normalized(self) -> bool {
246        // The allowed length is 1 +/- 1e-4, so the largest allowed
247        // squared length is (1 + 1e-4)^2 = 1.00020001, which makes
248        // the threshold for the squared length approximately 2e-4.
249        (self.length_squared() - 1.0).abs() <= 2e-4
250    }
251
252    /// Returns `true` if the rotation is near [`Rot2::IDENTITY`].
253    #[inline]
254    pub fn is_near_identity(self) -> bool {
255        // Same as `Quat::is_near_identity`, but using sine and cosine
256        let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;
257        self.cos > 0.0 && self.sin.abs() < threshold_angle_sin
258    }
259
260    /// Returns the angle in radians needed to make `self` and `other` coincide.
261    #[inline]
262    pub fn angle_between(self, other: Self) -> f32 {
263        (other * self.inverse()).as_radians()
264    }
265
266    /// Returns the inverse of the rotation. This is also the conjugate
267    /// of the unit complex number representing the rotation.
268    #[inline]
269    #[must_use]
270    #[doc(alias = "conjugate")]
271    pub fn inverse(self) -> Self {
272        Self {
273            cos: self.cos,
274            sin: -self.sin,
275        }
276    }
277
278    /// Performs a linear interpolation between `self` and `rhs` based on
279    /// the value `s`, and normalizes the rotation afterwards.
280    ///
281    /// When `s == 0.0`, the result will be equal to `self`.
282    /// When `s == 1.0`, the result will be equal to `rhs`.
283    ///
284    /// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result
285    /// when the difference between the two rotations is small. At larger differences,
286    /// the result resembles a kind of ease-in-out effect.
287    ///
288    /// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.
289    ///
290    /// # Details
291    ///
292    /// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn
293    /// between the endpoints of the arc formed by `self` and `rhs` on a unit circle,
294    /// and normalizing the result afterwards.
295    ///
296    /// Note that if the angles are opposite like 0 and π, the line will pass through the origin,
297    /// and the resulting angle will always be either `self` or `rhs` depending on `s`.
298    /// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`
299    /// will be returned as a fallback.
300    ///
301    /// # Example
302    ///
303    /// ```
304    /// # use bevy_math::Rot2;
305    /// #
306    /// let rot1 = Rot2::IDENTITY;
307    /// let rot2 = Rot2::degrees(135.0);
308    ///
309    /// let result1 = rot1.nlerp(rot2, 1.0 / 3.0);
310    /// assert_eq!(result1.as_degrees(), 28.675055);
311    ///
312    /// let result2 = rot1.nlerp(rot2, 0.5);
313    /// assert_eq!(result2.as_degrees(), 67.5);
314    /// ```
315    #[inline]
316    pub fn nlerp(self, end: Self, s: f32) -> Self {
317        Self {
318            sin: self.sin.lerp(end.sin, s),
319            cos: self.cos.lerp(end.cos, s),
320        }
321        .try_normalize()
322        // Fall back to the start rotation.
323        // This can happen when `self` and `end` are opposite angles and `s == 0.5`,
324        // because the resulting rotation would be zero, which cannot be normalized.
325        .unwrap_or(self)
326    }
327
328    /// Performs a spherical linear interpolation between `self` and `end`
329    /// based on the value `s`.
330    ///
331    /// This corresponds to interpolating between the two angles at a constant angular velocity.
332    ///
333    /// When `s == 0.0`, the result will be equal to `self`.
334    /// When `s == 1.0`, the result will be equal to `rhs`.
335    ///
336    /// If you would like the rotation to have a kind of ease-in-out effect, consider
337    /// using the slightly more efficient [`nlerp`](Self::nlerp) instead.
338    ///
339    /// # Example
340    ///
341    /// ```
342    /// # use bevy_math::Rot2;
343    /// #
344    /// let rot1 = Rot2::IDENTITY;
345    /// let rot2 = Rot2::degrees(135.0);
346    ///
347    /// let result1 = rot1.slerp(rot2, 1.0 / 3.0);
348    /// assert_eq!(result1.as_degrees(), 45.0);
349    ///
350    /// let result2 = rot1.slerp(rot2, 0.5);
351    /// assert_eq!(result2.as_degrees(), 67.5);
352    /// ```
353    #[inline]
354    pub fn slerp(self, end: Self, s: f32) -> Self {
355        self * Self::radians(self.angle_between(end) * s)
356    }
357}
358
359impl From<f32> for Rot2 {
360    /// Creates a [`Rot2`] from a counterclockwise angle in radians.
361    fn from(rotation: f32) -> Self {
362        Self::radians(rotation)
363    }
364}
365
366impl From<Rot2> for Mat2 {
367    /// Creates a [`Mat2`] rotation matrix from a [`Rot2`].
368    fn from(rot: Rot2) -> Self {
369        Mat2::from_cols_array(&[rot.cos, -rot.sin, rot.sin, rot.cos])
370    }
371}
372
373impl std::ops::Mul for Rot2 {
374    type Output = Self;
375
376    fn mul(self, rhs: Self) -> Self::Output {
377        Self {
378            cos: self.cos * rhs.cos - self.sin * rhs.sin,
379            sin: self.sin * rhs.cos + self.cos * rhs.sin,
380        }
381    }
382}
383
384impl std::ops::MulAssign for Rot2 {
385    fn mul_assign(&mut self, rhs: Self) {
386        *self = *self * rhs;
387    }
388}
389
390impl std::ops::Mul<Vec2> for Rot2 {
391    type Output = Vec2;
392
393    /// Rotates a [`Vec2`] by a [`Rot2`].
394    fn mul(self, rhs: Vec2) -> Self::Output {
395        Vec2::new(
396            rhs.x * self.cos - rhs.y * self.sin,
397            rhs.x * self.sin + rhs.y * self.cos,
398        )
399    }
400}
401
402#[cfg(any(feature = "approx", test))]
403impl approx::AbsDiffEq for Rot2 {
404    type Epsilon = f32;
405    fn default_epsilon() -> f32 {
406        f32::EPSILON
407    }
408    fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
409        self.cos.abs_diff_eq(&other.cos, epsilon) && self.sin.abs_diff_eq(&other.sin, epsilon)
410    }
411}
412
413#[cfg(any(feature = "approx", test))]
414impl approx::RelativeEq for Rot2 {
415    fn default_max_relative() -> f32 {
416        f32::EPSILON
417    }
418    fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
419        self.cos.relative_eq(&other.cos, epsilon, max_relative)
420            && self.sin.relative_eq(&other.sin, epsilon, max_relative)
421    }
422}
423
424#[cfg(any(feature = "approx", test))]
425impl approx::UlpsEq for Rot2 {
426    fn default_max_ulps() -> u32 {
427        4
428    }
429    fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
430        self.cos.ulps_eq(&other.cos, epsilon, max_ulps)
431            && self.sin.ulps_eq(&other.sin, epsilon, max_ulps)
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use approx::assert_relative_eq;
438
439    use crate::{Dir2, Rot2, Vec2};
440
441    #[test]
442    fn creation() {
443        let rotation1 = Rot2::radians(std::f32::consts::FRAC_PI_2);
444        let rotation2 = Rot2::degrees(90.0);
445        let rotation3 = Rot2::from_sin_cos(1.0, 0.0);
446
447        // All three rotations should be equal
448        assert_relative_eq!(rotation1.sin, rotation2.sin);
449        assert_relative_eq!(rotation1.cos, rotation2.cos);
450        assert_relative_eq!(rotation1.sin, rotation3.sin);
451        assert_relative_eq!(rotation1.cos, rotation3.cos);
452
453        // The rotation should be 90 degrees
454        assert_relative_eq!(rotation1.as_radians(), std::f32::consts::FRAC_PI_2);
455        assert_relative_eq!(rotation1.as_degrees(), 90.0);
456    }
457
458    #[test]
459    fn rotate() {
460        let rotation = Rot2::degrees(90.0);
461
462        assert_relative_eq!(rotation * Vec2::X, Vec2::Y);
463        assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X);
464    }
465
466    #[test]
467    fn add() {
468        let rotation1 = Rot2::degrees(90.0);
469        let rotation2 = Rot2::degrees(180.0);
470
471        // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the ]-180, 180] range
472        assert_eq!((rotation1 * rotation2).as_degrees(), -90.0);
473    }
474
475    #[test]
476    fn subtract() {
477        let rotation1 = Rot2::degrees(90.0);
478        let rotation2 = Rot2::degrees(45.0);
479
480        assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);
481
482        // This should be equivalent to the above
483        assert_relative_eq!(
484            rotation2.angle_between(rotation1),
485            std::f32::consts::FRAC_PI_4
486        );
487    }
488
489    #[test]
490    fn length() {
491        let rotation = Rot2 {
492            sin: 10.0,
493            cos: 5.0,
494        };
495
496        assert_eq!(rotation.length_squared(), 125.0);
497        assert_eq!(rotation.length(), 11.18034);
498        assert!((rotation.normalize().length() - 1.0).abs() < 10e-7);
499    }
500
501    #[test]
502    fn is_near_identity() {
503        assert!(!Rot2::radians(0.1).is_near_identity());
504        assert!(!Rot2::radians(-0.1).is_near_identity());
505        assert!(Rot2::radians(0.00001).is_near_identity());
506        assert!(Rot2::radians(-0.00001).is_near_identity());
507        assert!(Rot2::radians(0.0).is_near_identity());
508    }
509
510    #[test]
511    fn normalize() {
512        let rotation = Rot2 {
513            sin: 10.0,
514            cos: 5.0,
515        };
516        let normalized_rotation = rotation.normalize();
517
518        assert_eq!(normalized_rotation.sin, 0.89442724);
519        assert_eq!(normalized_rotation.cos, 0.44721362);
520
521        assert!(!rotation.is_normalized());
522        assert!(normalized_rotation.is_normalized());
523    }
524
525    #[test]
526    fn try_normalize() {
527        // Valid
528        assert!(Rot2 {
529            sin: 10.0,
530            cos: 5.0,
531        }
532        .try_normalize()
533        .is_some());
534
535        // NaN
536        assert!(Rot2 {
537            sin: f32::NAN,
538            cos: 5.0,
539        }
540        .try_normalize()
541        .is_none());
542
543        // Zero
544        assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());
545
546        // Non-finite
547        assert!(Rot2 {
548            sin: f32::INFINITY,
549            cos: 5.0,
550        }
551        .try_normalize()
552        .is_none());
553    }
554
555    #[test]
556    fn nlerp() {
557        let rot1 = Rot2::IDENTITY;
558        let rot2 = Rot2::degrees(135.0);
559
560        assert_eq!(rot1.nlerp(rot2, 1.0 / 3.0).as_degrees(), 28.675055);
561        assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
562        assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 67.5);
563        assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees(), 135.0);
564
565        let rot1 = Rot2::IDENTITY;
566        let rot2 = Rot2::from_sin_cos(0.0, -1.0);
567
568        assert!(rot1.nlerp(rot2, 1.0 / 3.0).is_near_identity());
569        assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
570        // At 0.5, there is no valid rotation, so the fallback is the original angle.
571        assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0);
572        assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees().abs(), 180.0);
573    }
574
575    #[test]
576    fn slerp() {
577        let rot1 = Rot2::IDENTITY;
578        let rot2 = Rot2::degrees(135.0);
579
580        assert_eq!(rot1.slerp(rot2, 1.0 / 3.0).as_degrees(), 45.0);
581        assert!(rot1.slerp(rot2, 0.0).is_near_identity());
582        assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 67.5);
583        assert_eq!(rot1.slerp(rot2, 1.0).as_degrees(), 135.0);
584
585        let rot1 = Rot2::IDENTITY;
586        let rot2 = Rot2::from_sin_cos(0.0, -1.0);
587
588        assert!((rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0).abs() < 10e-6);
589        assert!(rot1.slerp(rot2, 0.0).is_near_identity());
590        assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0);
591        assert_eq!(rot1.slerp(rot2, 1.0).as_degrees().abs(), 180.0);
592    }
593}