1use core::{
4 f32::consts::{PI, TAU},
5 fmt::{self, Debug, Display},
6 marker::PhantomData,
7 ops::{Add, Div, Mul, Neg, Rem, Sub},
8};
9
10use crate::math::{Affine, ApproxEq, Linear, Vector, vary::ZDiv};
11
12#[cfg(feature = "fp")]
13use crate::math::{Vec2, Vec3, float::f32, vec2, vec3};
14
15#[derive(Copy, Clone, Default, PartialEq)]
25#[repr(transparent)]
26pub struct Angle(f32);
27
28#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
30pub struct Polar<B>(PhantomData<B>);
31
32#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
34pub struct Spherical<B>(PhantomData<B>);
35
36pub type PolarVec<B = ()> = Vector<[f32; 2], Polar<B>>;
38
39pub type SphericalVec<B = ()> = Vector<[f32; 3], Spherical<B>>;
42
43pub fn rads(a: f32) -> Angle {
49 Angle(a)
50}
51
52pub fn degs(a: f32) -> Angle {
54 Angle(a * RADS_PER_DEG)
55}
56
57pub fn turns(a: f32) -> Angle {
59 Angle(a * RADS_PER_TURN)
60}
61
62#[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")]
96pub fn acos(x: f32) -> Angle {
97 Angle(f32::acos(x))
98}
99
100#[cfg(feature = "fp")]
113pub fn atan2(y: f32, x: f32) -> Angle {
114 Angle(f32::atan2(y, x))
115}
116
117pub const fn polar<B>(r: f32, az: Angle) -> PolarVec<B> {
119 Vector::new([r, az.to_rads()])
120}
121
122pub const fn spherical<B>(r: f32, az: Angle, alt: Angle) -> SphericalVec<B> {
127 Vector::new([r, az.to_rads(), alt.to_rads()])
128}
129
130const RADS_PER_DEG: f32 = PI / 180.0;
131const RADS_PER_TURN: f32 = TAU;
132
133impl Angle {
138 pub const ZERO: Self = Self(0.0);
140 pub const RIGHT: Self = Self(RADS_PER_TURN / 4.0);
142 pub const STRAIGHT: Self = Self(RADS_PER_TURN / 2.0);
144 pub const FULL: Self = Self(RADS_PER_TURN);
146
147 pub const fn to_rads(self) -> f32 {
156 self.0
157 }
158 pub fn to_degs(self) -> f32 {
165 self.0 / RADS_PER_DEG
166 }
167 pub fn to_turns(self) -> f32 {
175 self.0 / RADS_PER_TURN
176 }
177
178 pub fn min(self, other: Self) -> Self {
180 Self(self.0.min(other.0))
181 }
182 pub fn max(self, other: Self) -> Self {
184 Self(self.0.max(other.0))
185 }
186 #[must_use]
199 pub fn clamp(self, min: Self, max: Self) -> Self {
200 Self(self.0.clamp(min.0, max.0))
201 }
202}
203
204#[cfg(feature = "fp")]
205impl Angle {
206 pub fn sin(self) -> f32 {
215 f32::sin(self.0)
216 }
217 pub fn cos(self) -> f32 {
226 f32::cos(self.0)
227 }
228 pub fn sin_cos(self) -> (f32, f32) {
239 (self.sin(), self.cos())
240 }
241 pub fn tan(self) -> f32 {
248 f32::tan(self.0)
249 }
250
251 #[must_use]
262 pub fn wrap(self, min: Self, max: Self) -> Self {
263 Self(min.0 + f32::rem_euclid(self.0 - min.0, max.0 - min.0))
264 }
265}
266
267impl<B> PolarVec<B> {
268 #[inline]
270 pub fn r(&self) -> f32 {
271 self.0[0]
272 }
273 #[inline]
275 pub fn az(&self) -> Angle {
276 rads(self.0[1])
277 }
278
279 #[cfg(feature = "fp")]
307 pub fn to_cart(&self) -> Vec2<B> {
308 let (y, x) = self.az().sin_cos();
309 vec2(x, y) * self.r()
310 }
311}
312
313impl<B> SphericalVec<B> {
314 #[inline]
316 pub fn r(&self) -> f32 {
317 self.0[0]
318 }
319 #[inline]
321 pub fn az(&self) -> Angle {
322 rads(self.0[1])
323 }
324 #[inline]
326 pub fn alt(&self) -> Angle {
327 rads(self.0[2])
328 }
329
330 #[cfg(feature = "fp")]
335 pub fn to_cart(&self) -> Vec3<B> {
336 let (sin_alt, cos_alt) = self.alt().sin_cos();
337 let (sin_az, cos_az) = self.az().sin_cos();
338
339 let x = cos_az * cos_alt;
340 let z = sin_az * cos_alt;
341 let y = sin_alt;
342
343 self.r() * vec3(x, y, z)
344 }
345}
346
347#[cfg(feature = "fp")]
348impl<B> Vec2<B> {
349 pub fn to_polar(&self) -> PolarVec<B> {
385 let r = self.len();
386 let az = atan2(self.y(), self.x());
387 polar(r, az)
388 }
389}
390
391#[cfg(feature = "fp")]
392impl<B> Vec3<B> {
393 pub fn to_spherical(&self) -> SphericalVec<B> {
424 let [x, y, z] = self.0;
425 let az = atan2(z, x);
426 let alt = atan2(y, f32::sqrt(x * x + z * z));
427 let r = self.len();
428 spherical(r, az, alt)
429 }
430}
431
432impl ApproxEq for Angle {
437 fn approx_eq_eps(&self, other: &Self, eps: &Self) -> bool {
439 self.0.approx_eq_eps(&other.0, &eps.0)
440 }
441 fn relative_epsilon() -> Self {
442 Self(f32::relative_epsilon())
443 }
444}
445
446impl Affine for Angle {
447 type Space = ();
448 type Diff = Self;
449 const DIM: usize = 1;
450
451 #[inline]
452 fn add(&self, other: &Self) -> Self {
453 *self + *other
454 }
455 #[inline]
456 fn sub(&self, other: &Self) -> Self {
457 *self - *other
458 }
459}
460
461impl Linear for Angle {
462 type Scalar = f32;
463
464 #[inline]
465 fn zero() -> Self {
466 Self::ZERO
467 }
468 #[inline]
469 fn mul(&self, scalar: f32) -> Self {
470 *self * scalar
471 }
472}
473
474impl ZDiv for Angle {}
475
476impl<B> Default for SphericalVec<B> {
481 fn default() -> Self {
482 Self::new([1.0, 0.0, 0.0])
483 }
484}
485impl<B> Default for PolarVec<B> {
486 fn default() -> Self {
487 Self::new([1.0, 0.0])
488 }
489}
490
491impl Display for Angle {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 let (val, unit) = if f.alternate() {
494 (self.to_rads() / PI, "𝜋 rad")
495 } else {
496 (self.to_degs(), "°")
497 };
498 Display::fmt(&val, f)?;
499 f.write_str(unit)
500 }
501}
502
503impl Debug for Angle {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 f.write_str("Angle(")?;
506 Display::fmt(self, f)?;
507 f.write_str(")")
508 }
509}
510
511impl Add for Angle {
512 type Output = Self;
513 fn add(self, rhs: Self) -> Self {
514 Self(self.0 + rhs.0)
515 }
516}
517impl Sub for Angle {
518 type Output = Self;
519 fn sub(self, rhs: Self) -> Self {
520 Self(self.0 - rhs.0)
521 }
522}
523impl Neg for Angle {
524 type Output = Self;
525 fn neg(self) -> Self {
526 Self(-self.0)
527 }
528}
529
530impl Mul<f32> for Angle {
531 type Output = Self;
532 fn mul(self, rhs: f32) -> Self {
533 Self(self.0 * rhs)
534 }
535}
536impl Div<f32> for Angle {
537 type Output = Self;
538 fn div(self, rhs: f32) -> Self {
539 Self(self.0 / rhs)
540 }
541}
542impl Rem for Angle {
543 type Output = Self;
544 fn rem(self, rhs: Self) -> Self {
545 Self(self.0 % rhs.0)
546 }
547}
548
549#[cfg(feature = "fp")]
550impl<B> From<PolarVec<B>> for Vec2<B> {
551 fn from(p: PolarVec<B>) -> Self {
555 p.to_cart()
556 }
557}
558
559#[cfg(feature = "fp")]
560impl<B> From<Vec2<B>> for PolarVec<B> {
561 fn from(v: Vec2<B>) -> Self {
565 v.to_polar()
566 }
567}
568
569#[cfg(feature = "fp")]
570impl<B> From<SphericalVec<B>> for Vec3<B> {
571 fn from(v: SphericalVec<B>) -> Self {
575 v.to_cart()
576 }
577}
578
579#[cfg(feature = "fp")]
580impl<B> From<Vec3<B>> for SphericalVec<B> {
581 fn from(v: Vec3<B>) -> Self {
585 v.to_spherical()
586 }
587}
588
589#[cfg(test)]
590#[allow(unused, nonstandard_style)]
591mod tests {
592 use core::f32::consts::{PI, TAU};
593
594 use crate::{
595 assert_approx_eq,
596 math::{self, Lerp, Vary, Vec2, Vec3},
597 };
598
599 use super::*;
600
601 const SQRT_3: f32 = 1.7320508;
602
603 #[test]
604 fn rads_to_degs() {
605 assert_eq!(rads(PI).to_degs(), 180.0);
606 }
607
608 #[test]
609 fn rads_to_turns() {
610 assert_eq!(rads(PI).to_turns(), 0.5);
611 }
612
613 #[test]
614 fn degs_to_rads() {
615 assert_eq!(degs(180.0).to_rads(), PI);
616 }
617
618 #[test]
619 fn degs_to_turns() {
620 assert_eq!(degs(360.0).to_turns(), 1.0);
621 }
622
623 #[test]
624 fn turns_to_rads() {
625 assert_eq!(turns(1.0).to_rads(), TAU);
626 }
627
628 #[test]
629 fn turns_to_degs() {
630 assert_eq!(turns(1.0).to_degs(), 360.0);
631 }
632
633 #[test]
634 fn clamping() {
635 let min = degs(-45.0);
636 let max = degs(45.0);
637 assert_eq!(degs(60.0).clamp(min, max), max);
638 assert_eq!(degs(10.0).clamp(min, max), degs(10.0));
639 assert_eq!(degs(-50.0).clamp(min, max), min);
640 }
641
642 #[cfg(feature = "fp")]
643 #[test]
644 fn trig_functions() {
645 assert_eq!(degs(0.0).sin(), 0.0);
646 assert_eq!(degs(0.0).cos(), 1.0);
647
648 assert_approx_eq!(degs(30.0).sin(), 0.5);
649 assert_approx_eq!(degs(60.0).cos(), 0.5);
650
651 let (sin, cos) = degs(90.0).sin_cos();
652 assert_approx_eq!(sin, 1.0);
653 assert_approx_eq!(cos, 0.0);
654
655 assert_approx_eq!(degs(-45.0).tan(), -1.0);
656 assert_approx_eq!(degs(0.0).tan(), 0.0);
657 assert_approx_eq!(degs(45.0).tan(), 1.0);
658 assert_approx_eq!(degs(135.0).tan(), -1.0);
659 assert_approx_eq!(degs(225.0).tan(), 1.0);
660 assert_approx_eq!(degs(315.0).tan(), -1.0);
661 }
662
663 #[cfg(all(feature = "fp", not(feature = "mm")))]
665 #[test]
666 fn inverse_trig_functions() {
667 assert_approx_eq!(asin(-1.0), degs(-90.0));
668 assert_approx_eq!(asin(0.0), degs(0.0));
669 assert_approx_eq!(asin(0.5), degs(30.0));
670 assert_approx_eq!(asin(1.0), degs(90.0));
671
672 assert_approx_eq!(acos(-1.0), degs(180.0));
673 assert_approx_eq!(acos(0.0), degs(90.0));
674 assert_approx_eq!(acos(0.5), degs(60.0));
675 assert_approx_eq!(acos(1.0), degs(0.0));
676
677 assert_approx_eq!(atan2(0.0, 1.0), degs(0.0));
678 assert_approx_eq!(atan2(1.0, SQRT_3), degs(30.0));
679 assert_approx_eq!(atan2(1.0, -1.0), degs(135.0));
680 assert_approx_eq!(atan2(-SQRT_3, -1.0), degs(-120.0));
681 assert_approx_eq!(atan2(-1.0, 1.0), degs(-45.0));
682 }
683
684 #[cfg(feature = "fp")]
685 #[test]
686 fn wrapping() {
687 use crate::assert_approx_eq;
688
689 let a = degs(540.0).wrap(Angle::ZERO, Angle::FULL);
690 assert_approx_eq!(a, degs(180.0));
691
692 let a = degs(225.0).wrap(-Angle::STRAIGHT, Angle::STRAIGHT);
693 assert_approx_eq!(a, degs(-135.0));
694 }
695
696 #[test]
697 fn lerping() {
698 let a = degs(30.0).lerp(°s(60.0), 0.2);
699 assert_eq!(a, degs(36.0));
700 }
701
702 #[test]
703 fn varying() {
704 let mut i = degs(45.0).vary(degs(15.0), Some(4));
705
706 assert_approx_eq!(i.next(), Some(degs(45.0)));
707 assert_approx_eq!(i.next(), Some(degs(60.0)));
708 assert_approx_eq!(i.next(), Some(degs(75.0)));
709 assert_approx_eq!(i.next(), Some(degs(90.0)));
710 assert_approx_eq!(i.next(), None);
711 }
712
713 const vec2: fn(f32, f32) -> Vec2 = math::vec2;
714 const vec3: fn(f32, f32, f32) -> Vec3 = math::vec3;
715
716 #[cfg(feature = "fp")]
717 #[test]
718 fn polar_to_cartesian_zero_r() {
719 assert_eq!(polar(0.0, degs(0.0)).to_cart(), vec2(0.0, 0.0));
720 assert_eq!(polar(0.0, degs(30.0)).to_cart(), vec2(0.0, 0.0));
721 assert_eq!(polar(0.0, degs(-120.0)).to_cart(), vec2(0.0, 0.0));
722 }
723
724 #[cfg(feature = "fp")]
725 #[test]
726 fn polar_to_cartesian_zero_az() {
727 assert_eq!(polar(2.0, degs(0.0)).to_cart(), vec2(2.0, 0.0));
728 assert_eq!(polar(-3.0, degs(0.0)).to_cart(), vec2(-3.0, 0.0));
729 }
730
731 #[cfg(feature = "fp")]
732 #[test]
733 fn polar_to_cartesian() {
734 assert_approx_eq!(polar(2.0, degs(60.0)).to_cart(), vec2(1.0, SQRT_3));
735
736 assert_approx_eq!(
737 polar(3.0, degs(-90.0)).to_cart(),
738 vec2(0.0, -3.0),
739 eps = 1e-6
740 );
741 assert_approx_eq!(polar(4.0, degs(270.0)).to_cart(), vec2(0.0, -4.0));
742
743 assert_approx_eq!(
744 polar(5.0, turns(1.25)).to_cart(),
745 vec2(0.0, 5.0),
746 eps = 2e-6
747 );
748 }
749
750 #[cfg(feature = "fp")]
751 #[test]
752 fn cartesian_to_polar_zero_y() {
753 assert_approx_eq!(vec2(0.0, 0.0).to_polar(), polar(0.0, degs(0.0)));
754 assert_eq!(vec2(1.0, 0.0).to_polar(), polar(1.0, degs(0.0)));
755 }
756 #[cfg(feature = "fp")]
757 #[test]
758 fn cartesian_to_polar() {
759 assert_approx_eq!(vec2(SQRT_3, 1.0).to_polar(), polar(2.0, degs(30.0)));
760 assert_eq!(vec2(0.0, 2.0).to_polar(), polar(2.0, degs(90.0)));
761 assert_approx_eq!(vec2(-3.0, 0.0).to_polar(), polar(3.0, degs(180.0)));
762 assert_eq!(vec2(0.0, -4.0).to_polar(), polar(4.0, degs(-90.0)));
763 }
764
765 #[cfg(feature = "fp")]
766 #[test]
767 fn spherical_to_cartesian() {
768 let spherical = spherical::<()>;
769 assert_eq!(
770 spherical(0.0, degs(0.0), degs(0.0)).to_cart(),
771 vec3(0.0, 0.0, 0.0)
772 );
773 assert_eq!(
774 spherical(1.0, degs(0.0), degs(0.0)).to_cart(),
775 vec3(1.0, 0.0, 0.0)
776 );
777 assert_approx_eq!(
778 spherical(2.0, degs(60.0), degs(0.0)).to_cart(),
779 vec3(1.0, 0.0, SQRT_3)
780 );
781 assert_approx_eq!(
782 spherical(2.0, degs(90.0), degs(0.0)).to_cart(),
783 vec3(0.0, 0.0, 2.0)
784 );
785 assert_approx_eq!(
786 spherical(3.0, degs(123.0), degs(90.0)).to_cart(),
787 vec3(0.0, 3.0, 0.0)
788 );
789 }
790
791 #[cfg(feature = "fp")]
792 #[test]
793 fn cartesian_to_spherical_zero_alt() {
794 assert_approx_eq!(
795 vec3(0.0, 0.0, 0.0).to_spherical(),
796 spherical(0.0, degs(0.0), degs(0.0))
797 );
798 assert_eq!(
799 vec3(1.0, 0.0, 0.0).to_spherical(),
800 spherical(1.0, degs(0.0), degs(0.0))
801 );
802 assert_approx_eq!(
803 vec3(1.0, SQRT_3, 0.0).to_spherical(),
804 spherical(2.0, degs(0.0), degs(60.0))
805 );
806 assert_eq!(
807 vec3(0.0, 2.0, 0.0).to_spherical(),
808 spherical(2.0, degs(0.0), degs(90.0))
809 );
810 }
811
812 #[cfg(feature = "fp")]
813 #[test]
814 fn cartesian_to_spherical() {
815 use core::f32::consts::SQRT_2;
816 assert_approx_eq!(
817 vec3(SQRT_3, 0.0, 1.0).to_spherical(),
818 spherical(2.0, degs(30.0), degs(0.0))
819 );
820 assert_approx_eq!(
821 vec3(1.0, SQRT_2, 1.0).to_spherical(),
822 spherical(2.0, degs(45.0), degs(45.0))
823 );
824 assert_approx_eq!(
825 vec3(0.0, 0.0, 3.0).to_spherical(),
826 spherical(3.0, degs(90.0), degs(0.0))
827 );
828 }
829}