1use core::f32::consts::{PI, TAU};
4use core::fmt::{self, Debug, Display};
5use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
6
7use crate::math::approx::ApproxEq;
8use crate::math::space::{Affine, Linear};
9use crate::math::vec::Vector;
10
11#[cfg(feature = "fp")]
12use crate::math::float::f32;
13#[cfg(feature = "fp")]
14use crate::math::vec::{vec2, vec3, Vec2, Vec3};
15
16#[derive(Copy, Clone, Default, PartialEq)]
26#[repr(transparent)]
27pub struct Angle(f32);
28
29#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
31pub struct Polar;
32
33#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
35pub struct Spherical;
36
37pub type PolarVec = Vector<[f32; 2], Polar>;
39
40pub type SphericalVec = Vector<[f32; 3], Spherical>;
43
44pub fn rads(a: f32) -> Angle {
50 Angle(a)
51}
52
53pub fn degs(a: f32) -> Angle {
55 Angle(a * RADS_PER_DEG)
56}
57
58pub fn turns(a: f32) -> Angle {
60 Angle(a * RADS_PER_TURN)
61}
62
63#[cfg(feature = "fp")]
77pub fn asin(x: f32) -> Angle {
78 assert!(-1.0 <= x && x <= 1.0);
79 Angle(f32::asin(x))
80}
81
82#[cfg(feature = "fp")]
95pub fn acos(x: f32) -> Angle {
96 Angle(f32::acos(x))
97}
98
99#[cfg(feature = "fp")]
111pub fn atan2(y: f32, x: f32) -> Angle {
112 Angle(f32::atan2(y, x))
113}
114
115pub const fn polar(r: f32, az: Angle) -> PolarVec {
117 Vector::new([r, az.to_rads()])
118}
119
120pub const fn spherical(r: f32, az: Angle, alt: Angle) -> SphericalVec {
125 Vector::new([r, az.to_rads(), alt.to_rads()])
126}
127
128const RADS_PER_DEG: f32 = PI / 180.0;
129const RADS_PER_TURN: f32 = TAU;
130
131impl Angle {
136 pub const ZERO: Self = Self(0.0);
138 pub const RIGHT: Self = Self(RADS_PER_TURN / 4.0);
140 pub const STRAIGHT: Self = Self(RADS_PER_TURN / 2.0);
142 pub const FULL: Self = Self(RADS_PER_TURN);
144
145 pub const fn to_rads(self) -> f32 {
153 self.0
154 }
155 pub fn to_degs(self) -> f32 {
161 self.0 / RADS_PER_DEG
162 }
163 pub fn to_turns(self) -> f32 {
170 self.0 / RADS_PER_TURN
171 }
172
173 pub fn min(self, other: Self) -> Self {
175 Self(self.0.min(other.0))
176 }
177 pub fn max(self, other: Self) -> Self {
179 Self(self.0.max(other.0))
180 }
181 #[must_use]
195 pub fn clamp(self, min: Self, max: Self) -> Self {
196 Self(self.0.clamp(min.0, max.0))
197 }
198}
199
200#[cfg(feature = "fp")]
201impl Angle {
202 pub fn sin(self) -> f32 {
210 f32::sin(self.0)
211 }
212 pub fn cos(self) -> f32 {
220 f32::cos(self.0)
221 }
222 pub fn sin_cos(self) -> (f32, f32) {
232 (self.sin(), self.cos())
233 }
234 pub fn tan(self) -> f32 {
241 f32::tan(self.0)
242 }
243
244 #[must_use]
253 pub fn wrap(self, min: Self, max: Self) -> Self {
254 Self(min.0 + f32::rem_euclid(self.0 - min.0, max.0 - min.0))
255 }
256}
257
258impl PolarVec {
259 #[inline]
261 pub fn r(&self) -> f32 {
262 self.0[0]
263 }
264 #[inline]
266 pub fn az(&self) -> Angle {
267 rads(self.0[1])
268 }
269
270 #[cfg(feature = "fp")]
298 pub fn to_cart(&self) -> Vec2 {
299 let (y, x) = self.az().sin_cos();
300 vec2(x, y) * self.r()
301 }
302}
303
304impl SphericalVec {
305 #[inline]
307 pub fn r(&self) -> f32 {
308 self.0[0]
309 }
310 #[inline]
312 pub fn az(&self) -> Angle {
313 rads(self.0[1])
314 }
315 #[inline]
317 pub fn alt(&self) -> Angle {
318 rads(self.0[2])
319 }
320
321 #[cfg(feature = "fp")]
327 pub fn to_cart(&self) -> Vec3 {
328 let (sin_alt, cos_alt) = self.alt().sin_cos();
329 let (sin_az, cos_az) = self.az().sin_cos();
330
331 let x = cos_az * cos_alt;
332 let z = sin_az * cos_alt;
333 let y = sin_alt;
334
335 self.r() * vec3(x, y, z)
336 }
337}
338
339#[cfg(feature = "fp")]
340impl Vec2 {
341 pub fn to_polar(&self) -> PolarVec {
374 let r = self.len();
375 let az = atan2(self.y(), self.x());
376 polar(r, az)
377 }
378}
379
380#[cfg(feature = "fp")]
381impl Vec3 {
382 pub fn to_spherical(&self) -> SphericalVec {
412 let [x, y, z] = self.0;
413 let az = atan2(z, x);
414 let alt = atan2(y, f32::sqrt(x * x + z * z));
415 let r = self.len();
416 spherical(r, az, alt)
417 }
418}
419
420impl ApproxEq for Angle {
425 fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
427 self.0.approx_eq_eps(&other.0, &eps.0)
428 }
429 fn relative_epsilon() -> Self {
430 Self(f32::relative_epsilon())
431 }
432}
433
434impl Affine for Angle {
435 type Space = ();
436 type Diff = Self;
437 const DIM: usize = 1;
438
439 #[inline]
440 fn add(&self, other: &Self) -> Self {
441 *self + *other
442 }
443 #[inline]
444 fn sub(&self, other: &Self) -> Self {
445 *self - *other
446 }
447}
448
449impl Linear for Angle {
450 type Scalar = f32;
451
452 #[inline]
453 fn zero() -> Self {
454 Self::ZERO
455 }
456 #[inline]
457 fn neg(&self) -> Self {
458 -*self
459 }
460 #[inline]
461 fn mul(&self, scalar: f32) -> Self {
462 *self * scalar
463 }
464}
465
466impl Display for Angle {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 let (val, unit) = if f.alternate() {
473 (self.to_rads() / PI, "𝜋 rad")
474 } else {
475 (self.to_degs(), "°")
476 };
477 Display::fmt(&val, f)?;
478 f.write_str(unit)
479 }
480}
481
482impl Debug for Angle {
483 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484 f.write_str("Angle(")?;
485 Display::fmt(self, f)?;
486 f.write_str(")")
487 }
488}
489
490impl Add for Angle {
491 type Output = Self;
492 fn add(self, rhs: Self) -> Self {
493 Self(self.0 + rhs.0)
494 }
495}
496impl Sub for Angle {
497 type Output = Self;
498 fn sub(self, rhs: Self) -> Self {
499 Self(self.0 - rhs.0)
500 }
501}
502impl Neg for Angle {
503 type Output = Self;
504 fn neg(self) -> Self {
505 Self(-self.0)
506 }
507}
508
509impl Mul<f32> for Angle {
510 type Output = Self;
511 fn mul(self, rhs: f32) -> Self {
512 Self(self.0 * rhs)
513 }
514}
515impl Div<f32> for Angle {
516 type Output = Self;
517 fn div(self, rhs: f32) -> Self {
518 Self(self.0 / rhs)
519 }
520}
521impl Rem for Angle {
522 type Output = Self;
523 fn rem(self, rhs: Self) -> Self {
524 Self(self.0 % rhs.0)
525 }
526}
527
528#[cfg(feature = "fp")]
529impl From<PolarVec> for Vec2 {
530 fn from(p: PolarVec) -> Self {
534 p.to_cart()
535 }
536}
537
538#[cfg(feature = "fp")]
539impl From<Vec2> for PolarVec {
540 fn from(v: Vec2) -> Self {
544 v.to_polar()
545 }
546}
547
548#[cfg(feature = "fp")]
549impl From<SphericalVec> for Vec3 {
550 fn from(v: SphericalVec) -> Self {
554 v.to_cart()
555 }
556}
557
558#[cfg(feature = "fp")]
559impl From<Vec3> for SphericalVec {
560 fn from(v: Vec3) -> Self {
564 v.to_spherical()
565 }
566}
567
568#[cfg(test)]
569#[allow(unused)]
570mod tests {
571 use core::f32::consts::{PI, TAU};
572
573 use crate::assert_approx_eq;
574 use crate::math::vary::Vary;
575
576 use super::*;
577
578 const SQRT_3: f32 = 1.7320508;
579
580 #[test]
581 fn rads_to_degs() {
582 assert_eq!(rads(PI).to_degs(), 180.0);
583 }
584
585 #[test]
586 fn rads_to_turns() {
587 assert_eq!(rads(PI).to_turns(), 0.5);
588 }
589
590 #[test]
591 fn degs_to_rads() {
592 assert_eq!(degs(180.0).to_rads(), PI);
593 }
594
595 #[test]
596 fn degs_to_turns() {
597 assert_eq!(degs(360.0).to_turns(), 1.0);
598 }
599
600 #[test]
601 fn turns_to_rads() {
602 assert_eq!(turns(1.0).to_rads(), TAU);
603 }
604
605 #[test]
606 fn turns_to_degs() {
607 assert_eq!(turns(1.0).to_degs(), 360.0);
608 }
609
610 #[test]
611 fn clamping() {
612 let min = degs(-45.0);
613 let max = degs(45.0);
614 assert_eq!(degs(60.0).clamp(min, max), max);
615 assert_eq!(degs(10.0).clamp(min, max), degs(10.0));
616 assert_eq!(degs(-50.0).clamp(min, max), min);
617 }
618
619 #[cfg(feature = "fp")]
620 #[test]
621 fn trig_functions() {
622 assert_eq!(degs(0.0).sin(), 0.0);
623 assert_eq!(degs(0.0).cos(), 1.0);
624
625 assert_approx_eq!(degs(30.0).sin(), 0.5);
626 assert_approx_eq!(degs(60.0).cos(), 0.5);
627
628 let (sin, cos) = degs(90.0).sin_cos();
629 assert_approx_eq!(sin, 1.0);
630 assert_approx_eq!(cos, 0.0);
631
632 assert_approx_eq!(degs(-45.0).tan(), -1.0);
633 assert_approx_eq!(degs(0.0).tan(), 0.0);
634 assert_approx_eq!(degs(45.0).tan(), 1.0);
635 assert_approx_eq!(degs(135.0).tan(), -1.0);
636 assert_approx_eq!(degs(225.0).tan(), 1.0);
637 assert_approx_eq!(degs(315.0).tan(), -1.0);
638 }
639
640 #[cfg(all(feature = "fp", not(feature = "mm")))]
642 #[test]
643 fn inverse_trig_functions() {
644 assert_approx_eq!(asin(-1.0), degs(-90.0));
645 assert_approx_eq!(asin(0.0), degs(0.0));
646 assert_approx_eq!(asin(0.5), degs(30.0));
647 assert_approx_eq!(asin(1.0), degs(90.0));
648
649 assert_approx_eq!(acos(-1.0), degs(180.0));
650 assert_approx_eq!(acos(0.0), degs(90.0));
651 assert_approx_eq!(acos(0.5), degs(60.0));
652 assert_approx_eq!(acos(1.0), degs(0.0));
653
654 assert_approx_eq!(atan2(0.0, 1.0), degs(0.0));
655 assert_approx_eq!(atan2(1.0, SQRT_3), degs(30.0));
656 assert_approx_eq!(atan2(1.0, -1.0), degs(135.0));
657 assert_approx_eq!(atan2(-SQRT_3, -1.0), degs(-120.0));
658 assert_approx_eq!(atan2(-1.0, 1.0), degs(-45.0));
659 }
660
661 #[cfg(feature = "fp")]
662 #[test]
663 fn wrapping() {
664 use crate::assert_approx_eq;
665
666 let a = degs(540.0).wrap(Angle::ZERO, Angle::FULL);
667 assert_approx_eq!(a, degs(180.0));
668
669 let a = degs(225.0).wrap(-Angle::STRAIGHT, Angle::STRAIGHT);
670 assert_approx_eq!(a, degs(-135.0));
671 }
672
673 #[test]
674 fn lerping() {
675 let a = degs(30.0).lerp(°s(60.0), 0.2);
676 assert_eq!(a, degs(36.0));
677 }
678
679 #[test]
680 fn varying() {
681 let mut i = degs(45.0).vary(degs(15.0), Some(4));
682
683 assert_approx_eq!(i.next(), Some(degs(45.0)));
684 assert_approx_eq!(i.next(), Some(degs(60.0)));
685 assert_approx_eq!(i.next(), Some(degs(75.0)));
686 assert_approx_eq!(i.next(), Some(degs(90.0)));
687 assert_approx_eq!(i.next(), None);
688 }
689
690 #[cfg(feature = "fp")]
691 #[test]
692 fn polar_to_cartesian_zero_r() {
693 assert_eq!(polar(0.0, degs(0.0)).to_cart(), vec2(0.0, 0.0));
694 assert_eq!(polar(0.0, degs(30.0)).to_cart(), vec2(0.0, 0.0));
695 assert_eq!(polar(0.0, degs(-120.0)).to_cart(), vec2(0.0, 0.0));
696 }
697
698 #[cfg(feature = "fp")]
699 #[test]
700 fn polar_to_cartesian_zero_az() {
701 assert_eq!(polar(2.0, degs(0.0)).to_cart(), vec2(2.0, 0.0));
702 assert_eq!(polar(-3.0, degs(0.0)).to_cart(), vec2(-3.0, 0.0));
703 }
704
705 #[cfg(feature = "fp")]
706 #[test]
707 fn polar_to_cartesian() {
708 assert_approx_eq!(polar(2.0, degs(60.0)).to_cart(), vec2(1.0, SQRT_3));
709
710 assert_approx_eq!(
711 polar(3.0, degs(-90.0)).to_cart(),
712 vec2(0.0, -3.0),
713 eps = 1e-6
714 );
715 assert_approx_eq!(polar(4.0, degs(270.0)).to_cart(), vec2(0.0, -4.0));
716
717 assert_approx_eq!(
718 polar(5.0, turns(1.25)).to_cart(),
719 vec2(0.0, 5.0),
720 eps = 2e-6
721 );
722 }
723
724 #[cfg(feature = "fp")]
725 #[test]
726 fn cartesian_to_polar_zero_y() {
727 assert_approx_eq!(vec2(0.0, 0.0).to_polar(), polar(0.0, degs(0.0)));
728 assert_eq!(vec2(1.0, 0.0).to_polar(), polar(1.0, degs(0.0)));
729 }
730 #[cfg(feature = "fp")]
731 #[test]
732 fn cartesian_to_polar() {
733 assert_approx_eq!(vec2(SQRT_3, 1.0).to_polar(), polar(2.0, degs(30.0)));
734 assert_eq!(vec2(0.0, 2.0).to_polar(), polar(2.0, degs(90.0)));
735 assert_approx_eq!(vec2(-3.0, 0.0).to_polar(), polar(3.0, degs(180.0)));
736 assert_eq!(vec2(0.0, -4.0).to_polar(), polar(4.0, degs(-90.0)));
737 }
738
739 #[cfg(feature = "fp")]
740 #[test]
741 fn spherical_to_cartesian() {
742 assert_eq!(
743 spherical(0.0, degs(0.0), degs(0.0)).to_cart(),
744 vec3(0.0, 0.0, 0.0)
745 );
746 assert_eq!(
747 spherical(1.0, degs(0.0), degs(0.0)).to_cart(),
748 vec3(1.0, 0.0, 0.0)
749 );
750 assert_approx_eq!(
751 spherical(2.0, degs(60.0), degs(0.0)).to_cart(),
752 vec3(1.0, 0.0, SQRT_3)
753 );
754 assert_approx_eq!(
755 spherical(2.0, degs(90.0), degs(0.0)).to_cart(),
756 vec3(0.0, 0.0, 2.0)
757 );
758 assert_approx_eq!(
759 spherical(3.0, degs(123.0), degs(90.0)).to_cart(),
760 vec3(0.0, 3.0, 0.0)
761 );
762 }
763
764 #[cfg(feature = "fp")]
765 #[test]
766 fn cartesian_to_spherical_zero_alt() {
767 assert_approx_eq!(
768 vec3(0.0, 0.0, 0.0).to_spherical(),
769 spherical(0.0, degs(0.0), degs(0.0))
770 );
771 assert_eq!(
772 vec3(1.0, 0.0, 0.0).to_spherical(),
773 spherical(1.0, degs(0.0), degs(0.0))
774 );
775 assert_approx_eq!(
776 vec3(1.0, SQRT_3, 0.0).to_spherical(),
777 spherical(2.0, degs(0.0), degs(60.0))
778 );
779 assert_eq!(
780 vec3(0.0, 2.0, 0.0).to_spherical(),
781 spherical(2.0, degs(0.0), degs(90.0))
782 );
783 }
784
785 #[cfg(feature = "fp")]
786 #[test]
787 fn cartesian_to_spherical() {
788 use core::f32::consts::SQRT_2;
789 assert_approx_eq!(
790 vec3(SQRT_3, 0.0, 1.0).to_spherical(),
791 spherical(2.0, degs(30.0), degs(0.0))
792 );
793 assert_approx_eq!(
794 vec3(1.0, SQRT_2, 1.0).to_spherical(),
795 spherical(2.0, degs(45.0), degs(45.0))
796 );
797 assert_approx_eq!(
798 vec3(0.0, 0.0, 3.0).to_spherical(),
799 spherical(3.0, degs(90.0), degs(0.0))
800 );
801 }
802}