bevy_math/
rotation2d.rs

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