firefly_rust/graphics/
angle.rs1use crate::*;
2use core::f32::consts::{FRAC_PI_2, PI, TAU};
3use core::ops::*;
4
5#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
9pub struct Angle(pub(crate) f32);
10
11impl Angle {
12 pub const FULL_CIRCLE: Angle = Angle(TAU);
14 pub const HALF_CIRCLE: Angle = Angle(PI);
16 pub const QUARTER_CIRCLE: Angle = Angle(FRAC_PI_2);
18 pub const ZERO: Angle = Angle(0.);
20
21 #[must_use]
23 pub fn from_degrees(d: f32) -> Self {
24 Self(d * PI / 180.0)
25 }
26
27 #[must_use]
29 pub const fn from_radians(r: f32) -> Self {
30 Self(r)
31 }
32
33 #[must_use]
35 pub fn abs(self) -> Self {
36 Self(math::abs(self.0))
37 }
38
39 #[must_use]
41 pub fn normalize(self) -> Self {
42 Self(math::rem_euclid(self.0, TAU))
43 }
44
45 #[must_use]
47 pub fn to_degrees(self) -> f32 {
48 180. * self.0 / PI
49 }
50
51 #[must_use]
53 pub const fn to_radians(self) -> f32 {
54 self.0
55 }
56
57 #[must_use]
59 pub fn sin(&self) -> f32 {
60 math::sin(self.0)
61 }
62
63 #[must_use]
65 pub fn cos(&self) -> f32 {
66 math::cos(self.0)
67 }
68
69 #[must_use]
71 pub fn tan(&self) -> f32 {
72 math::tan(self.0)
73 }
74}
75
76impl Add for Angle {
77 type Output = Self;
78
79 fn add(self, other: Angle) -> Self {
80 Angle(self.0 + other.0)
81 }
82}
83
84impl AddAssign for Angle {
85 fn add_assign(&mut self, other: Angle) {
86 self.0 += other.0;
87 }
88}
89
90impl Sub for Angle {
91 type Output = Self;
92
93 fn sub(self, other: Angle) -> Self {
94 Angle(self.0 - other.0)
95 }
96}
97
98impl SubAssign for Angle {
99 fn sub_assign(&mut self, other: Angle) {
100 self.0 -= other.0;
101 }
102}
103
104impl Neg for Angle {
105 type Output = Self;
106
107 fn neg(self) -> Self {
108 Angle(-self.0)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_from_degrees() {
118 let d = Angle::from_degrees;
119 let r = Angle::from_radians;
120 assert_eq!(d(0.), r(0.));
121 assert_eq!(d(180.), r(PI));
122 assert_eq!(d(360.), r(TAU));
123 assert_eq!(d(90.), r(PI / 2.));
124 }
125
126 #[test]
127 fn test_abs() {
128 let a = Angle::from_degrees;
129 assert_eq!(a(90.), a(90.));
130 assert_ne!(a(-90.), a(90.));
131 assert_eq!(a(-90.).abs(), a(90.));
132 }
133
134 #[test]
135 fn test_normalize() {
136 let a = Angle::from_degrees;
137 assert_eq!(a(90.).normalize(), a(90.));
138 assert_eq!(a(360.).normalize(), a(0.));
139 assert_eq!(a(-90.).normalize(), a(270.));
140 }
141
142 #[test]
143 #[expect(clippy::float_cmp)]
144 fn test_to_degrees() {
145 let a = Angle::from_degrees;
146 assert_eq!(a(47.).to_degrees(), 47.);
147 assert_eq!(a(0.).to_degrees(), 0.);
148 assert_eq!(a(90.).to_degrees(), 90.);
149 assert_eq!(a(370.).to_degrees(), 370.);
150 }
151}