nuit_core/utils/
angle.rs

1use std::{f64::consts::PI, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}};
2
3use nuit_derive::ApproxEq;
4use serde::{Deserialize, Serialize};
5
6/// A geometric angle.
7#[derive(Debug, Default, Clone, Copy, PartialEq, ApproxEq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct Angle {
10    radians: f64,
11}
12
13impl Angle {
14    pub const ZERO: Self = Self::with_radians(0.0);
15    pub const QUARTER: Self = Self::with_radians(PI / 2.0);
16    pub const HALF: Self = Self::with_radians(PI);
17    pub const FULL: Self = Self::with_radians(2.0 * PI);
18
19    /// Creates an angle from the given value in radians.
20    pub const fn with_radians(radians: f64) -> Self {
21        Self { radians }
22    }
23
24    /// Creates an angle from the given value in degrees.
25    pub const fn with_degrees(degrees: f64) -> Self {
26        Self::with_radians(degrees * PI / 180.0)
27    }
28
29    /// Creates an angle from the given fractional value between 0 and 1.
30    pub const fn with_fractional(fractional: f64) -> Self {
31        Self::with_radians(fractional * 2.0 * PI)
32    }
33
34    /// The value in radians.
35    pub const fn radians(self) -> f64 {
36        self.radians
37    }
38
39    /// The value in degrees.
40    pub const fn degrees(self) -> f64 {
41        self.radians * 180.0 / PI
42    }
43
44    /// The fractional value between 0 and 1.
45    pub const fn fractional(self) -> f64 {
46        self.radians / (2.0 * PI)
47    }
48}
49
50impl Add for Angle {
51    type Output = Self;
52
53    fn add(self, rhs: Self) -> Self {
54        Self { radians: self.radians + rhs.radians }
55    }
56}
57
58impl Sub for Angle {
59    type Output = Self;
60
61    fn sub(self, rhs: Self) -> Self {
62        Self { radians: self.radians - rhs.radians }
63    }
64}
65
66impl Mul<f64> for Angle {
67    type Output = Self;
68
69    fn mul(self, rhs: f64) -> Self {
70        Self { radians: self.radians * rhs }
71    }
72}
73
74impl Div<f64> for Angle {
75    type Output = Self;
76
77    fn div(self, rhs: f64) -> Self {
78        Self { radians: self.radians / rhs }
79    }
80}
81
82impl AddAssign for Angle {
83    fn add_assign(&mut self, rhs: Self) {
84        self.radians += rhs.radians;
85    }
86}
87
88impl SubAssign for Angle {
89    fn sub_assign(&mut self, rhs: Self) {
90        self.radians -= rhs.radians;
91    }
92}
93
94impl MulAssign<f64> for Angle {
95    fn mul_assign(&mut self, rhs: f64) {
96        self.radians *= rhs;
97    }
98}
99
100impl DivAssign<f64> for Angle {
101    fn div_assign(&mut self, rhs: f64) {
102        self.radians /= rhs;
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use std::f64::consts::PI;
109
110    use crate::{assert_approx_eq, Angle};
111
112    #[test]
113    fn conversions() {
114        assert_approx_eq!(Angle::HALF.fractional(), 0.5);
115        assert_approx_eq!(Angle::HALF.degrees(), 180.0);
116        assert_approx_eq!(Angle::HALF.radians(), PI);
117
118        assert_approx_eq!(Angle::with_fractional(1.0), Angle::FULL);
119        assert_approx_eq!(Angle::with_degrees(360.0), Angle::FULL);
120        assert_approx_eq!(Angle::with_radians(2.0 * PI), Angle::FULL);
121    }
122}