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}