retrofire_core/math/
angle.rs

1//! Angular quantities, including scalar angles and angular vectors.
2
3use 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//
16// Types
17//
18
19/// A scalar angular quantity.
20///
21/// Prevents confusion between degrees and radians by requiring the use of
22/// one of the named constructors to create an `Angle`, as well as one of
23/// the named getter methods to obtain the angle as a raw `f32` value.
24#[derive(Copy, Clone, Default, PartialEq)]
25#[repr(transparent)]
26pub struct Angle(f32);
27
28/// Tag type for a polar coordinate space
29#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
30pub struct Polar<B>(PhantomData<B>);
31
32/// Tag type for a spherical coordinate space.
33#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
34pub struct Spherical<B>(PhantomData<B>);
35
36/// A polar coordinate vector, with radius and azimuth components.
37pub type PolarVec<B = ()> = Vector<[f32; 2], Polar<B>>;
38
39/// A spherical coordinate vector, with radius, azimuth, and altitude
40/// (elevation) components.
41pub type SphericalVec<B = ()> = Vector<[f32; 3], Spherical<B>>;
42
43//
44// Free fns and consts
45//
46
47/// Returns an angle of `a` radians.
48pub fn rads(a: f32) -> Angle {
49    Angle(a)
50}
51
52/// Returns an angle of `a` degrees.
53pub fn degs(a: f32) -> Angle {
54    Angle(a * RADS_PER_DEG)
55}
56
57/// Returns an angle of `a` turns.
58pub fn turns(a: f32) -> Angle {
59    Angle(a * RADS_PER_TURN)
60}
61
62/// Returns the arcsine of `x` as an `Angle`.
63///
64/// The return value is in the range [-90°, 90°].
65///
66/// # Examples
67/// ```
68/// use retrofire_core::assert_approx_eq;
69/// use retrofire_core::math::{degs, asin};
70///
71/// assert_approx_eq!(asin(1.0), degs(90.0));
72/// assert_approx_eq!(asin(-1.0), degs(-90.0));
73/// ```
74/// # Panics
75/// If `x` is outside the range [-1.0, 1.0].
76#[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/// Returns the arccosine of `x` as an `Angle`.
83///
84/// The return value is in the range [-90°, 90°].
85///
86/// # Examples
87/// ```
88/// use retrofire_core::assert_approx_eq;
89/// use retrofire_core::math::{acos, degs};
90///
91/// assert_approx_eq!(acos(1.0), degs(0.0));
92/// ```
93/// # Panics
94/// If `x` is outside the range [-1.0, 1.0].
95#[cfg(feature = "fp")]
96pub fn acos(x: f32) -> Angle {
97    Angle(f32::acos(x))
98}
99
100/// Returns the four-quadrant arctangent of `y` and `x` as an `Angle`.
101///
102/// The returned angle is equal to [`y.atan2(x)`][f32::atan2].
103///
104/// # Examples
105/// ```
106/// use retrofire_core::math::{atan2, degs};
107///
108/// assert_eq!(atan2(0.0, 1.0), degs(0.0));
109/// assert_eq!(atan2(2.0, 2.0), degs(45.0));
110/// assert_eq!(atan2(3.0, 0.0), degs(90.0));
111/// ```
112#[cfg(feature = "fp")]
113pub fn atan2(y: f32, x: f32) -> Angle {
114    Angle(f32::atan2(y, x))
115}
116
117/// Returns a polar coordinate vector with azimuth `az` and radius `r`.
118pub const fn polar<B>(r: f32, az: Angle) -> PolarVec<B> {
119    Vector::new([r, az.to_rads()])
120}
121
122/// Returns a spherical coordinate vector with azimuth `az`,
123/// altitude `alt`, and radius `r`.
124///
125/// An altitude of +90° corresponds to straight up and -90° to straight down.
126pub 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
133//
134// Inherent impls
135//
136
137impl Angle {
138    /// A zero degree angle.
139    pub const ZERO: Self = Self(0.0);
140    /// A 90 degree angle.
141    pub const RIGHT: Self = Self(RADS_PER_TURN / 4.0);
142    /// A 180 degree angle.
143    pub const STRAIGHT: Self = Self(RADS_PER_TURN / 2.0);
144    /// A 360 degree angle.
145    pub const FULL: Self = Self(RADS_PER_TURN);
146
147    /// Returns the value of `self` in radians.
148    /// # Examples
149    /// ```
150    /// use std::f32;
151    /// use retrofire_core::math::degs;
152    ///
153    /// assert_eq!(degs(90.0).to_rads(), f32::consts::FRAC_PI_2);
154    /// ```
155    pub const fn to_rads(self) -> f32 {
156        self.0
157    }
158    /// Returns the value of `self` in degrees.
159    /// # Examples
160    /// ```
161    /// use retrofire_core::math::turns;
162    ///
163    /// assert_eq!(turns(2.0).to_degs(), 720.0);
164    pub fn to_degs(self) -> f32 {
165        self.0 / RADS_PER_DEG
166    }
167    /// Returns the value of `self` in turns.
168    /// # Examples
169    /// ```
170    /// use retrofire_core::math::degs;
171    ///
172    /// assert_eq!(degs(180.0).to_turns(), 0.5);
173    /// ```
174    pub fn to_turns(self) -> f32 {
175        self.0 / RADS_PER_TURN
176    }
177
178    /// Returns the minimum of `self` and `other`.
179    pub fn min(self, other: Self) -> Self {
180        Self(self.0.min(other.0))
181    }
182    /// Returns the maximum of `self` and `other`.
183    pub fn max(self, other: Self) -> Self {
184        Self(self.0.max(other.0))
185    }
186    /// Returns `self` clamped to the range `min..=max`.
187    ///
188    /// # Examples
189    /// ```
190    /// use retrofire_core::math::degs;
191    ///
192    /// let (min, max) = (degs(0.0), degs(45.0));
193    ///
194    /// assert_eq!(degs(100.0).clamp(min, max), max);
195    /// assert_eq!(degs(30.0).clamp(min, max), degs(30.0));
196    /// assert_eq!(degs(-10.0).clamp(min, max), min);
197    /// ```
198    #[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    /// Returns the sine of `self`.
207    /// # Examples
208    /// ```
209    /// use retrofire_core::assert_approx_eq;
210    /// use retrofire_core::math::degs;
211    ///
212    /// assert_approx_eq!(degs(30.0).sin(), 0.5)
213    /// ```
214    pub fn sin(self) -> f32 {
215        f32::sin(self.0)
216    }
217    /// Returns the cosine of `self`.
218    /// # Examples
219    /// ```
220    /// use retrofire_core::assert_approx_eq;
221    /// use retrofire_core::math::degs;
222    ///
223    /// assert_approx_eq!(degs(60.0).cos(), 0.5)
224    /// ```
225    pub fn cos(self) -> f32 {
226        f32::cos(self.0)
227    }
228    /// Simultaneously computes the sine and cosine of `self`.
229    /// # Examples
230    /// ```
231    /// use retrofire_core::assert_approx_eq;
232    /// use retrofire_core::math::degs;
233    ///
234    /// let (sin, cos) = degs(90.0).sin_cos();
235    /// assert_approx_eq!(sin, 1.0);
236    /// assert_approx_eq!(cos, 0.0);
237    /// ```
238    pub fn sin_cos(self) -> (f32, f32) {
239        (self.sin(), self.cos())
240    }
241    /// Returns the tangent of `self`.
242    /// # Examples
243    /// ```
244    /// use retrofire_core::math::degs;
245    /// assert_eq!(degs(45.0).tan(), 1.0)
246    /// ```
247    pub fn tan(self) -> f32 {
248        f32::tan(self.0)
249    }
250
251    /// Returns `self` "wrapped around" to the range `min..max`.
252    ///
253    /// # Examples
254    /// ```
255    /// use retrofire_core::assert_approx_eq;
256    /// use retrofire_core::math::{degs, turns};
257    ///
258    /// // 400 (mod 360) = 40
259    /// assert_approx_eq!(degs(400.0).wrap(turns(0.0), turns(1.0)), degs(40.0))
260    /// ```
261    #[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    /// Returns the radial component of `self`.
269    #[inline]
270    pub fn r(&self) -> f32 {
271        self.0[0]
272    }
273    /// Returns the azimuthal component of `self`.
274    #[inline]
275    pub fn az(&self) -> Angle {
276        rads(self.0[1])
277    }
278
279    /// Returns `self` converted to the equivalent Cartesian 2-vector.
280    ///
281    /// Let the components of `self` be `(r, az)`. Then the result `(x, y)`
282    /// equals `(r * cos(az), r * sin(az))`.
283    ///
284    /// ```text
285    /// +y
286    /// ^     ^
287    /// | +r /
288    /// |   /
289    /// |  /_ +az
290    /// | /  \
291    /// +----------> +x
292    /// ```
293    ///
294    /// # Examples
295    /// ```
296    /// use retrofire_core::assert_approx_eq;
297    /// use retrofire_core::math::{vec2, polar, degs};
298    ///
299    /// let vec2 = vec2::<f32, ()>;
300    ///
301    /// assert_approx_eq!(polar(2.0, degs(0.0)).to_cart(), vec2(2.0, 0.0));
302    /// assert_approx_eq!(polar(3.0, degs(90.0)).to_cart(), vec2(0.0, 3.0));
303    /// assert_approx_eq!(polar(4.0, degs(-180.0)).to_cart(), vec2(-4.0, 0.0));
304    ///
305    /// ```
306    #[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    /// Returns the radial component of `self`.
315    #[inline]
316    pub fn r(&self) -> f32 {
317        self.0[0]
318    }
319    /// Returns the azimuthal component of `self`.
320    #[inline]
321    pub fn az(&self) -> Angle {
322        rads(self.0[1])
323    }
324    /// Returns the altitude (elevation) component of `self`.
325    #[inline]
326    pub fn alt(&self) -> Angle {
327        rads(self.0[2])
328    }
329
330    /// Returns `self` converted to the equivalent Cartesian 3-vector.
331    ///
332    /// # Examples
333    /// TODO examples
334    #[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    /// Returns `self` converted into the equivalent polar coordinate vector.
350    ///
351    /// The `r` component of the result equals `self.len()`.
352    ///
353    /// The `az` component equals the angle between the vector and the x-axis
354    /// in the range (-180°, 180°] such that positive `y` maps to positive `az`.
355    /// ```text
356    /// +y
357    /// ^    ^
358    /// |   /
359    /// |  /_ +az
360    /// | /  \
361    /// +----------> +x
362    /// ```
363    ///
364    /// # Examples
365    /// ```
366    /// use retrofire_core::assert_approx_eq;
367    /// use retrofire_core::math::{vec2, degs};
368    ///
369    /// let vec2 = vec2::<f32, ()>;
370    ///
371    /// // A non-negative x and zero y maps to zero azimuth
372    /// assert_eq!(vec2(0.0, 0.0).to_polar().az(), degs(0.0));
373    /// assert_eq!(vec2(1.0, 0.0).to_polar().az(), degs(0.0));
374    ///
375    /// // A zero x and positive y maps to right angle azimuth
376    /// assert_eq!(vec2(0.0, 1.0).to_polar().az(), degs(90.0));
377    ///
378    /// // A zero x and negative y maps to negative right angle azimuth
379    /// assert_eq!(vec2(0.0, -1.0).to_polar().az(), degs(-90.0));
380    ///
381    /// // A negative x and zero y maps to straight angle azimuth
382    /// assert_approx_eq!(vec2(-1.0, 0.0).to_polar().az(), degs(180.0));
383    /// ```
384    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    /// Converts `self` into the equivalent spherical coordinate vector.
394    ///
395    /// The `r` component of the result equals `self.len()`.
396    ///
397    /// The `az` component is the angle between `self` and the xy-plane in the
398    /// range (-180°, 180°] such that positive `z` maps to positive `az`.
399    ///
400    /// The `alt` component is the angle between `self` and the xz-plane in the
401    /// range [-90°, 90°] such that positive `y` maps to positive `alt`.
402    ///
403    /// # Examples
404    /// ```
405    /// use retrofire_core::math::{vec3, spherical, degs};
406    ///
407    /// // The positive x-axis lies at zero azimuth and altitude
408    /// assert_eq!(
409    ///     vec3(2.0, 0.0, 0.0).to_spherical(),
410    ///     spherical::<()>(2.0, degs(0.0), degs(0.0))
411    /// );
412    /// // The positive y-axis lies at 90° altitude
413    /// assert_eq!(
414    ///     vec3(0.0, 2.0, 0.0).to_spherical(),
415    ///     spherical::<()>(2.0, degs(0.0), degs(90.0))
416    /// );
417    /// // The positive z axis lies at 90° azimuth
418    /// assert_eq!(
419    ///     vec3(0.0, 0.0, 2.0).to_spherical(),
420    ///     spherical::<()>(2.0, degs(90.0), degs(0.0))
421    /// );
422    /// ```
423    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
432//
433// Local trait impls
434//
435
436impl ApproxEq for Angle {
437    // TODO Should this account for wraparound?
438    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
476//
477// Foreign trait impls
478//
479
480impl<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    /// Converts a polar vector into the equivalent Cartesian vector.
552    ///
553    /// See [PolarVec::to_cart] for more information.
554    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    /// Converts a Cartesian 2-vector into the equivalent polar vector.
562    ///
563    /// See [Vec2::to_polar] for more information.
564    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    /// Converts a spherical coordinate vector to a Euclidean 3-vector.
572    ///
573    /// See [SphericalVec::to_cart] for more information.
574    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    /// Converts a Cartesian 3-vector into the equivalent spherical vector.
582    ///
583    /// See [Vec3::to_spherical] for more information.
584    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    // TODO Micromath requires large epsilon here
664    #[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(&degs(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}