retrofire_core/math/
angle.rs

1//! Angular quantities, including scalar angles and angular vectors.
2
3use 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//
17// Types
18//
19
20/// A scalar angular quantity.
21///
22/// Prevents confusion between degrees and radians by requiring the use of
23/// one of the named constructors to create an `Angle`, as well as one of
24/// the named getter methods to obtain the angle as a raw `f32` value.
25#[derive(Copy, Clone, Default, PartialEq)]
26#[repr(transparent)]
27pub struct Angle(f32);
28
29/// Tag type for a polar coordinate space
30#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
31pub struct Polar;
32
33/// Tag type for a spherical coordinate space.
34#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
35pub struct Spherical;
36
37/// A polar coordinate vector, with radius and azimuth components.
38pub type PolarVec = Vector<[f32; 2], Polar>;
39
40/// A spherical coordinate vector, with radius, azimuth, and altitude
41/// (elevation) components.
42pub type SphericalVec = Vector<[f32; 3], Spherical>;
43
44//
45// Free fns and consts
46//
47
48/// Returns an angle of `a` radians.
49pub fn rads(a: f32) -> Angle {
50    Angle(a)
51}
52
53/// Returns an angle of `a` degrees.
54pub fn degs(a: f32) -> Angle {
55    Angle(a * RADS_PER_DEG)
56}
57
58/// Returns an angle of `a` turns.
59pub fn turns(a: f32) -> Angle {
60    Angle(a * RADS_PER_TURN)
61}
62
63/// Returns the arcsine of `x` as an `Angle`.
64///
65/// The return value is in the range [-90°, 90°].
66///
67/// # Examples
68/// ```
69/// # use retrofire_core::assert_approx_eq;
70/// # use retrofire_core::math::angle::*;
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::angle::*;
90/// assert_approx_eq!(acos(1.0), degs(0.0));
91/// ```
92/// # Panics
93/// If `x` is outside the range [-1.0, 1.0].
94#[cfg(feature = "fp")]
95pub fn acos(x: f32) -> Angle {
96    Angle(f32::acos(x))
97}
98
99/// Returns the four-quadrant arctangent of `y` and `x` as an `Angle`.
100///
101/// The returned angle is equal to [`y.atan2(x)`][f32::atan2].
102///
103/// # Examples
104/// ```
105/// # use retrofire_core::math::angle::*;
106/// assert_eq!(atan2(0.0, 1.0), degs(0.0));
107/// assert_eq!(atan2(2.0, 2.0), degs(45.0));
108/// assert_eq!(atan2(3.0, 0.0), degs(90.0));
109/// ```
110#[cfg(feature = "fp")]
111pub fn atan2(y: f32, x: f32) -> Angle {
112    Angle(f32::atan2(y, x))
113}
114
115/// Returns a polar coordinate vector with azimuth `az` and radius `r`.
116pub const fn polar(r: f32, az: Angle) -> PolarVec {
117    Vector::new([r, az.to_rads()])
118}
119
120/// Returns a spherical coordinate vector with azimuth `az`,
121/// altitude `alt`, and radius `r`.
122///
123/// An altitude of +90° corresponds to straight up and -90° to straight down.
124pub 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
131//
132// Inherent impls
133//
134
135impl Angle {
136    /// A zero degree angle.
137    pub const ZERO: Self = Self(0.0);
138    /// A 90 degree angle.
139    pub const RIGHT: Self = Self(RADS_PER_TURN / 4.0);
140    /// A 180 degree angle.
141    pub const STRAIGHT: Self = Self(RADS_PER_TURN / 2.0);
142    /// A 360 degree angle.
143    pub const FULL: Self = Self(RADS_PER_TURN);
144
145    /// Returns the value of `self` in radians.
146    /// # Examples
147    /// ```
148    /// # use std::f32;
149    /// # use retrofire_core::math::degs;
150    /// assert_eq!(degs(90.0).to_rads(), f32::consts::FRAC_PI_2);
151    /// ```
152    pub const fn to_rads(self) -> f32 {
153        self.0
154    }
155    /// Returns the value of `self` in degrees.
156    /// # Examples
157    /// ```
158    /// # use retrofire_core::math::turns;
159    /// assert_eq!(turns(2.0).to_degs(), 720.0);
160    pub fn to_degs(self) -> f32 {
161        self.0 / RADS_PER_DEG
162    }
163    /// Returns the value of `self` in turns.
164    /// # Examples
165    /// ```
166    /// # use retrofire_core::math::degs;
167    /// assert_eq!(degs(180.0).to_turns(), 0.5);
168    /// ```
169    pub fn to_turns(self) -> f32 {
170        self.0 / RADS_PER_TURN
171    }
172
173    /// Returns the minimum of `self` and `other`.
174    pub fn min(self, other: Self) -> Self {
175        Self(self.0.min(other.0))
176    }
177    /// Returns the maximum of `self` and `other`.
178    pub fn max(self, other: Self) -> Self {
179        Self(self.0.max(other.0))
180    }
181    /// Returns `self` clamped to the range `min..=max`.
182    ///
183    /// # Examples
184    /// ```
185    /// # use retrofire_core::math::angle::degs;
186    /// let (min, max) = (degs(0.0), degs(45.0));
187    ///
188    /// assert_eq!(degs(100.0).clamp(min, max), max);
189    ///
190    /// assert_eq!(degs(30.0).clamp(min, max), degs(30.0));
191    ///
192    /// assert_eq!(degs(-10.0).clamp(min, max), min);
193    /// ```
194    #[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    /// Returns the sine of `self`.
203    /// # Examples
204    /// ```
205    /// # use retrofire_core::assert_approx_eq;
206    /// # use retrofire_core::math::angle::*;
207    /// assert_approx_eq!(degs(30.0).sin(), 0.5)
208    /// ```
209    pub fn sin(self) -> f32 {
210        f32::sin(self.0)
211    }
212    /// Returns the cosine of `self`.
213    /// # Examples
214    /// ```
215    /// # use retrofire_core::assert_approx_eq;
216    /// # use retrofire_core::math::angle::*;
217    /// assert_approx_eq!(degs(60.0).cos(), 0.5)
218    /// ```
219    pub fn cos(self) -> f32 {
220        f32::cos(self.0)
221    }
222    /// Simultaneously computes the sine and cosine of `self`.
223    /// # Examples
224    /// ```
225    /// # use retrofire_core::assert_approx_eq;
226    /// # use retrofire_core::math::angle::*;
227    /// let (sin, cos) = degs(90.0).sin_cos();
228    /// assert_approx_eq!(sin, 1.0);
229    /// assert_approx_eq!(cos, 0.0);
230    /// ```
231    pub fn sin_cos(self) -> (f32, f32) {
232        (self.sin(), self.cos())
233    }
234    /// Returns the tangent of `self`.
235    /// # Examples
236    /// ```
237    /// # use retrofire_core::math::angle::*;
238    /// assert_eq!(degs(45.0).tan(), 1.0)
239    /// ```
240    pub fn tan(self) -> f32 {
241        f32::tan(self.0)
242    }
243
244    /// Returns `self` "wrapped around" to the range `min..max`.
245    ///
246    /// # Examples
247    /// ```
248    /// # use retrofire_core::assert_approx_eq;
249    /// # use retrofire_core::math::angle::*;
250    /// assert_approx_eq!(degs(400.0).wrap(turns(0.0), turns(1.0)), degs(40.0))
251    /// ```
252    #[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    /// Returns the radial component of `self`.
260    #[inline]
261    pub fn r(&self) -> f32 {
262        self.0[0]
263    }
264    /// Returns the azimuthal component of `self`.
265    #[inline]
266    pub fn az(&self) -> Angle {
267        rads(self.0[1])
268    }
269
270    /// Returns `self` converted to the equivalent Cartesian 2-vector.
271    ///
272    /// Let the components of self be `(r, az)`. Then the `x` component of the
273    /// result equals `r * cos(az)`, and the `y` component equals `r * sin(az)`.
274    ///
275    /// ```text
276    /// +y
277    /// ^     ^
278    /// | +r /
279    /// |   /
280    /// |  /_ +az
281    /// | /  \
282    /// +----------> +x
283    /// ```
284    ///
285    /// # Examples
286    /// ```
287    /// # use retrofire_core::assert_approx_eq;
288    /// # use retrofire_core::math::angle::{polar, degs};
289    /// # use retrofire_core::math::vec::vec2;
290    /// assert_approx_eq!(polar(2.0, degs(0.0)).to_cart(), vec2(2.0, 0.0));
291    ///
292    /// assert_approx_eq!(polar(2.0, degs(90.0)).to_cart(), vec2(0.0, 2.0));
293    ///
294    /// assert_approx_eq!(polar(2.0, degs(-180.0)).to_cart(), vec2(-2.0, 0.0));
295    ///
296    /// ```
297    #[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    /// Returns the radial component of `self`.
306    #[inline]
307    pub fn r(&self) -> f32 {
308        self.0[0]
309    }
310    /// Returns the azimuthal component of `self`.
311    #[inline]
312    pub fn az(&self) -> Angle {
313        rads(self.0[1])
314    }
315    /// Returns the altitude (elevation) component of `self`.
316    #[inline]
317    pub fn alt(&self) -> Angle {
318        rads(self.0[2])
319    }
320
321    /// Returns `self` converted to the equivalent Cartesian 3-vector.
322    ///
323    /// # Examples
324    ///
325    /// TODO examples
326    #[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    /// Returns `self` converted into the equivalent polar coordinate vector.
342    ///
343    /// The `r` component of the result equals `self.len()`.
344    ///
345    /// The `az` component equals the angle between the vector and the x-axis
346    /// in the range (-180°, 180°] such that positive `y` maps to positive `az`.
347    /// ```text
348    /// +y
349    /// ^    ^
350    /// |   /
351    /// |  /_ +az
352    /// | /  \
353    /// +----------> +x
354    /// ```
355    ///
356    /// # Examples
357    /// ```
358    /// # use retrofire_core::assert_approx_eq;
359    /// # use retrofire_core::math::{*, angle::*};
360    /// // A non-negative x and zero y maps to zero azimuth
361    /// assert_eq!(vec2(0.0, 0.0).to_polar().az(), degs(0.0));
362    /// assert_eq!(vec2(1.0, 0.0).to_polar().az(), degs(0.0));
363    ///
364    /// // A zero x and positive y maps to right angle azimuth
365    /// assert_eq!(vec2(0.0, 1.0).to_polar().az(), degs(90.0));
366    ///
367    /// // A zero x and negative y maps to negative right angle azimuth
368    /// assert_eq!(vec2(0.0, -1.0).to_polar().az(), degs(-90.0));
369    ///
370    /// // A negative x and zero y maps to straight angle azimuth
371    /// assert_approx_eq!(vec2(-1.0, 0.0).to_polar().az(), degs(180.0));
372    /// ```
373    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    /// Converts `self` into the equivalent spherical coordinate vector.
383    ///
384    /// The `r` component of the result equals `self.len()`.
385    ///
386    /// The `az` component is the angle between `self` and the xy-plane in the
387    /// range (-180°, 180°] such that positive `z` maps to positive `az`.
388    ///
389    /// The `alt` component is the angle between `self` and the xz-plane in the
390    /// range [-90°, 90°] such that positive `y` maps to positive `alt`.
391    ///
392    /// # Examples
393    /// ```
394    /// # use retrofire_core::math::{*, angle::*};
395    /// // The positive x-axis lies at zero azimuth and altitude
396    /// assert_eq!(
397    ///     vec3(2.0, 0.0, 0.0).to_spherical(),
398    ///     spherical(2.0, degs(0.0), degs(0.0))
399    /// );
400    /// // The positive y-axis lies at 90° altitude
401    /// assert_eq!(
402    ///     vec3(0.0, 2.0, 0.0).to_spherical(),
403    ///     spherical(2.0, degs(0.0), degs(90.0))
404    /// );
405    /// // The positive z axis lies at 90° azimuth
406    /// assert_eq!(
407    ///     vec3(0.0, 0.0, 2.0).to_spherical(),
408    ///     spherical(2.0, degs(90.0), degs(0.0))
409    /// );
410    /// ```
411    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
420//
421// Local trait impls
422//
423
424impl ApproxEq for Angle {
425    // TODO Should this account for wraparound?
426    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
466//
467// Foreign trait impls
468//
469
470impl 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    /// Converts a polar vector into the equivalent Cartesian vector.
531    ///
532    /// See [PolarVec::to_cart] for more information.
533    fn from(p: PolarVec) -> Self {
534        p.to_cart()
535    }
536}
537
538#[cfg(feature = "fp")]
539impl From<Vec2> for PolarVec {
540    /// Converts a Cartesian 2-vector into the equivalent polar vector.
541    ///
542    /// See [Vec2::to_polar] for more information.
543    fn from(v: Vec2) -> Self {
544        v.to_polar()
545    }
546}
547
548#[cfg(feature = "fp")]
549impl From<SphericalVec> for Vec3 {
550    /// Converts a spherical coordinate vector to a Euclidean 3-vector.
551    ///
552    /// See [SphericalVec::to_cart] for more information.
553    fn from(v: SphericalVec) -> Self {
554        v.to_cart()
555    }
556}
557
558#[cfg(feature = "fp")]
559impl From<Vec3> for SphericalVec {
560    /// Converts a Cartesian 3-vector into the equivalent spherical vector.
561    ///
562    /// See [Vec3::to_spherical] for more information.
563    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    // TODO Micromath requires large epsilon here
641    #[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(&degs(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}