1use super::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
5#[serde(transparent)]
6pub struct Angle<T: Float = f32> {
7 radians: T,
8}
9
10impl<T: Float> rand::distributions::Distribution<Angle<T>> for rand::distributions::Standard {
11 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Angle<T> {
12 Angle::from_radians(rng.gen_range(T::ZERO..T::from_f32(2.0 * f32::PI)))
13 }
14}
15
16impl<T: Float> Angle<T> {
17 pub const ZERO: Self = Self { radians: T::ZERO };
19
20 pub fn map<U: Float>(&self, f: impl Fn(T) -> U) -> Angle<U> {
22 Angle {
23 radians: f(self.radians),
24 }
25 }
26
27 pub fn acos(cos: T) -> Self {
31 Self {
32 radians: cos.acos(),
33 }
34 }
35
36 pub fn asin(sin: T) -> Self {
40 Self {
41 radians: sin.asin(),
42 }
43 }
44
45 pub fn atan(tan: T) -> Self {
49 Self {
50 radians: tan.atan(),
51 }
52 }
53
54 pub fn atan2(y: T, x: T) -> Self {
61 Self {
62 radians: T::atan2(y, x),
63 }
64 }
65
66 pub fn sin(&self) -> T {
68 self.radians.sin()
69 }
70
71 pub fn cos(&self) -> T {
73 self.radians.cos()
74 }
75
76 pub fn sin_cos(&self) -> (T, T) {
79 self.radians.sin_cos()
80 }
81
82 pub fn tan(self) -> T {
84 self.radians.tan()
85 }
86
87 pub fn from_radians(radians: T) -> Self {
89 Self { radians }
90 }
91
92 pub fn from_degrees(degrees: T) -> Self {
94 Self {
95 radians: degrees_to_radians(degrees),
96 }
97 }
98
99 pub fn as_radians(&self) -> T {
101 self.radians
102 }
103
104 pub fn as_degrees(&self) -> T {
106 radians_to_degrees(self.radians)
107 }
108
109 pub fn normalized_2pi(&self) -> Self {
111 let tau = T::PI + T::PI;
112 let mut norm = (self.radians / tau).fract();
113 if norm < T::ZERO {
114 norm += T::ONE;
115 }
116 Self {
117 radians: norm * tau,
118 }
119 }
120
121 pub fn abs(&self) -> Self {
123 Self {
124 radians: self.radians.abs(),
125 }
126 }
127
128 pub fn normalized_pi(&self) -> Self {
130 let pi = T::PI;
131 let mut angle = self.normalized_2pi().radians;
132 if angle > pi {
133 angle -= pi + pi;
134 }
135 Self { radians: angle }
136 }
137
138 pub fn angle_from(&self, from: Self) -> Self {
140 from.angle_to(*self)
141 }
142
143 pub fn angle_to(&self, target: Self) -> Self {
145 let pi = T::PI;
146 let mut delta = target.normalized_2pi().radians - self.normalized_2pi().radians;
147 if delta.abs() > pi {
148 delta -= (pi + pi) * delta.signum();
149 }
150 Self { radians: delta }
151 }
152
153 pub fn unit_vec(&self) -> vec2<T> {
155 let (sin, cos) = self.radians.sin_cos();
156 vec2(cos, sin)
157 }
158}
159
160fn degrees_to_radians<T: Float>(degrees: T) -> T {
161 degrees / T::from_f32(180.0) * T::PI
162}
163
164fn radians_to_degrees<T: Float>(radians: T) -> T {
165 radians / T::PI * T::from_f32(180.0)
166}
167
168impl<T: Float> Mul<T> for Angle<T> {
169 type Output = Self;
170 fn mul(self, rhs: T) -> Self::Output {
171 Self {
172 radians: self.radians * rhs,
173 }
174 }
175}
176
177impl<T: Float> Div<T> for Angle<T> {
178 type Output = Self;
179 fn div(self, rhs: T) -> Self::Output {
180 Self {
181 radians: self.radians / rhs,
182 }
183 }
184}
185
186impl<T: Float> Add for Angle<T> {
187 type Output = Self;
188 fn add(self, rhs: Self) -> Self::Output {
189 Self {
190 radians: self.radians + rhs.radians,
191 }
192 }
193}
194
195impl<T: Float> AddAssign for Angle<T> {
196 fn add_assign(&mut self, rhs: Self) {
197 *self = self.add(rhs);
198 }
199}
200
201impl<T: Float> Sub for Angle<T> {
202 type Output = Self;
203 fn sub(self, rhs: Self) -> Self::Output {
204 Self {
205 radians: self.radians - rhs.radians,
206 }
207 }
208}
209
210impl<T: Float> SubAssign for Angle<T> {
211 fn sub_assign(&mut self, rhs: Self) {
212 *self = self.sub(rhs);
213 }
214}
215
216impl<T: Float> Neg for Angle<T> {
217 type Output = Self;
218 fn neg(self) -> Self::Output {
219 Self {
220 radians: -self.radians,
221 }
222 }
223}
224
225#[test]
226fn test_angle_conversion() {
227 const EPSILON: f32 = 1e-3;
228 let tests = [
229 (0.0, 0.0),
230 (90.0, f32::PI / 2.0),
231 (180.0, f32::PI),
232 (270.0, f32::PI * 3.0 / 2.0),
233 (360.0, f32::PI * 2.0),
234 ];
235 for (degrees, radians) in tests {
236 let d = Angle::from_degrees(degrees).as_radians();
237 let r = Angle::from_radians(radians).as_radians();
238 let delta = r - d;
239 assert!(
240 delta.abs() < EPSILON,
241 "{degrees} degrees expected to be converted to {radians} radians, found {d}"
242 )
243 }
244}
245
246#[test]
247fn test_angle_normalize_2pi() {
248 const EPSILON: f32 = 1e-3;
249 let tests = [0.0, f32::PI, f32::PI / 2.0, f32::PI * 3.0 / 2.0];
250 for test in tests {
251 for offset in [0, 1, -1, 2, -2] {
252 let angle = test + f32::PI * 2.0 * offset as f32;
253 let norm = Angle::from_radians(angle).normalized_2pi().as_radians();
254 let delta = test - norm;
255 assert!(
256 delta.abs() < EPSILON,
257 "Normalized {angle} expected to be {test}, found {norm}"
258 );
259 }
260 }
261}
262
263#[test]
264fn test_angle_delta() {
265 const EPSILON: f32 = 1e-3;
266 let tests = [
267 (0.0, f32::PI / 2.0, f32::PI / 2.0),
268 (0.0, f32::PI * 3.0 / 2.0, -f32::PI / 2.0),
269 ];
270 for (from, to, test) in tests {
271 for offset_from in [0, 1, -1, 2, -2] {
272 for offset_to in [0, 1, -1, 2, -2] {
273 for offset in [0.0, 1.0, -1.0, 2.0, -2.0] {
274 let from = from + f32::PI * 2.0 * offset_from as f32 + offset;
275 let to = to + f32::PI * 2.0 * offset_to as f32 + offset;
276 let angle = Angle::from_radians(from)
277 .angle_to(Angle::from_radians(to))
278 .as_radians();
279 let delta = test - angle;
280 assert!(
281 delta.abs() < EPSILON,
282 "Angle from {from} to {to} expected to be {test}, found {angle}"
283 );
284 }
285 }
286 }
287 }
288}