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#[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 pub cos: f32,
50 pub sin: f32,
54}
55
56impl Default for Rot2 {
57 fn default() -> Self {
58 Self::IDENTITY
59 }
60}
61
62impl Rot2 {
63 pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
65
66 pub const PI: Self = Self {
68 cos: -1.0,
69 sin: 0.0,
70 };
71
72 pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
74
75 pub const FRAC_PI_3: Self = Self {
77 cos: 0.5,
78 sin: 0.866_025_4,
79 };
80
81 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 pub const FRAC_PI_6: Self = Self {
89 cos: 0.866_025_4,
90 sin: 0.5,
91 };
92
93 pub const FRAC_PI_8: Self = Self {
95 cos: 0.923_879_5,
96 sin: 0.382_683_43,
97 };
98
99 #[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 #[inline]
115 pub fn degrees(degrees: f32) -> Self {
116 Self::radians(degrees.to_radians())
117 }
118
119 #[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 #[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 #[inline]
151 pub fn as_degrees(self) -> f32 {
152 self.as_radians().to_degrees()
153 }
154
155 #[inline]
157 pub const fn sin_cos(self) -> (f32, f32) {
158 (self.sin, self.cos)
159 }
160
161 #[inline]
167 #[doc(alias = "norm")]
168 pub fn length(self) -> f32 {
169 Vec2::new(self.sin, self.cos).length()
170 }
171
172 #[inline]
181 #[doc(alias = "norm2")]
182 pub fn length_squared(self) -> f32 {
183 Vec2::new(self.sin, self.cos).length_squared()
184 }
185
186 #[inline]
190 pub fn length_recip(self) -> f32 {
191 Vec2::new(self.sin, self.cos).length_recip()
192 }
193
194 #[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 #[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 #[inline]
231 pub fn is_finite(self) -> bool {
232 self.sin.is_finite() && self.cos.is_finite()
233 }
234
235 #[inline]
237 pub fn is_nan(self) -> bool {
238 self.sin.is_nan() || self.cos.is_nan()
239 }
240
241 #[inline]
245 pub fn is_normalized(self) -> bool {
246 (self.length_squared() - 1.0).abs() <= 2e-4
250 }
251
252 #[inline]
254 pub fn is_near_identity(self) -> bool {
255 let threshold_angle_sin = 0.000_049_692_047; self.cos > 0.0 && self.sin.abs() < threshold_angle_sin
258 }
259
260 #[inline]
262 pub fn angle_between(self, other: Self) -> f32 {
263 (other * self.inverse()).as_radians()
264 }
265
266 #[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 #[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 .unwrap_or(self)
326 }
327
328 #[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 fn from(rotation: f32) -> Self {
362 Self::radians(rotation)
363 }
364}
365
366impl From<Rot2> for Mat2 {
367 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 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 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 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 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 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 assert!(Rot2 {
529 sin: 10.0,
530 cos: 5.0,
531 }
532 .try_normalize()
533 .is_some());
534
535 assert!(Rot2 {
537 sin: f32::NAN,
538 cos: 5.0,
539 }
540 .try_normalize()
541 .is_none());
542
543 assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());
545
546 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 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}