zng_unit/
angle.rs

1use crate::{about_eq_hash, about_eq_ord};
2
3use super::{EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, about_eq};
4
5use std::{
6    f32::consts::{PI, TAU},
7    fmt, ops,
8};
9
10/// Angle in radians.
11///
12/// See [`AngleUnits`] for more details.
13///
14/// # Equality
15///
16/// Equality is determined using [`about_eq`] with `0.00001` granularity.
17#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
18#[serde(transparent)]
19pub struct AngleRadian(pub f32);
20impl ops::Add for AngleRadian {
21    type Output = Self;
22
23    fn add(self, rhs: Self) -> Self::Output {
24        Self(self.0 + rhs.0)
25    }
26}
27impl ops::AddAssign for AngleRadian {
28    fn add_assign(&mut self, rhs: Self) {
29        self.0 += rhs.0;
30    }
31}
32impl ops::Sub for AngleRadian {
33    type Output = Self;
34
35    fn sub(self, rhs: Self) -> Self::Output {
36        Self(self.0 - rhs.0)
37    }
38}
39impl ops::SubAssign for AngleRadian {
40    fn sub_assign(&mut self, rhs: Self) {
41        self.0 -= rhs.0;
42    }
43}
44impl ops::Neg for AngleRadian {
45    type Output = Self;
46
47    fn neg(self) -> Self::Output {
48        Self(-self.0)
49    }
50}
51impl AngleRadian {
52    /// Radians in `[0.0 ..= TAU]`.
53    pub fn modulo(self) -> Self {
54        AngleRadian(self.0.rem_euclid(TAU))
55    }
56
57    /// Linear interpolation.
58    pub fn lerp(self, to: Self, factor: Factor) -> Self {
59        Self(lerp(self.0, to.0, factor))
60    }
61
62    /// Spherical linear interpolation.
63    ///
64    /// Always uses the shortest path from `self` to `to`.
65    ///
66    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
67    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
68    /// normalized.
69    ///
70    /// [`lerp`]: Self::lerp
71    pub fn slerp(self, to: Self, factor: Factor) -> Self {
72        Self(slerp(self.0, to.0, TAU, factor))
73    }
74}
75
76impl PartialEq for AngleRadian {
77    fn eq(&self, other: &Self) -> bool {
78        about_eq(self.0, other.0, EQ_GRANULARITY)
79    }
80}
81impl Eq for AngleRadian {}
82impl std::hash::Hash for AngleRadian {
83    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
84        about_eq_hash(self.0, EQ_GRANULARITY, state);
85    }
86}
87impl Ord for AngleRadian {
88    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
89        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
90    }
91}
92impl PartialOrd for AngleRadian {
93    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
94        Some(self.cmp(other))
95    }
96}
97
98impl From<AngleGradian> for AngleRadian {
99    fn from(grad: AngleGradian) -> Self {
100        AngleRadian(grad.0 * PI / 200.0)
101    }
102}
103impl From<AngleDegree> for AngleRadian {
104    fn from(deg: AngleDegree) -> Self {
105        AngleRadian(deg.0.to_radians())
106    }
107}
108impl From<AngleTurn> for AngleRadian {
109    fn from(turn: AngleTurn) -> Self {
110        AngleRadian(turn.0 * TAU)
111    }
112}
113impl From<AngleRadian> for euclid::Angle<f32> {
114    fn from(rad: AngleRadian) -> Self {
115        euclid::Angle::radians(rad.0)
116    }
117}
118
119impl fmt::Debug for AngleRadian {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        if f.alternate() {
122            f.debug_tuple("AngleRadian").field(&self.0).finish()
123        } else {
124            write!(f, "{}.rad()", self.0)
125        }
126    }
127}
128impl fmt::Display for AngleRadian {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{} rad", self.0)
131    }
132}
133
134/// Parses `"##"`, `"## rad"` and `"##.rad()"` where `##` is a `f32`.
135impl std::str::FromStr for AngleRadian {
136    type Err = std::num::ParseFloatError;
137
138    fn from_str(s: &str) -> Result<Self, Self::Err> {
139        crate::parse_suffix(s, &[" rad", "rad", ".rad()"]).map(AngleRadian)
140    }
141}
142
143/// Angle in gradians.
144///
145/// See [`AngleUnits`] for more details.
146///
147/// # Equality
148///
149/// Equality is determined using [`about_eq`] with `0.001` granularity.
150#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
151#[serde(transparent)]
152pub struct AngleGradian(pub f32);
153impl AngleGradian {
154    /// Gradians in `[0.0 ..= 400.0]`.
155    pub fn modulo(self) -> Self {
156        AngleGradian(self.0.rem_euclid(400.0))
157    }
158
159    /// Linear interpolation.
160    pub fn lerp(self, to: Self, factor: Factor) -> Self {
161        Self(lerp(self.0, to.0, factor))
162    }
163
164    /// Spherical linear interpolation.
165    ///
166    /// Always uses the shortest path from `self` to `to`.
167    ///
168    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
169    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
170    /// normalized.
171    ///
172    /// [`lerp`]: Self::lerp
173    pub fn slerp(self, to: Self, factor: Factor) -> Self {
174        Self(slerp(self.0, to.0, 400.0, factor))
175    }
176}
177impl ops::Add for AngleGradian {
178    type Output = Self;
179
180    fn add(self, rhs: Self) -> Self::Output {
181        Self(self.0 + rhs.0)
182    }
183}
184impl ops::AddAssign for AngleGradian {
185    fn add_assign(&mut self, rhs: Self) {
186        self.0 += rhs.0;
187    }
188}
189impl ops::Sub for AngleGradian {
190    type Output = Self;
191
192    fn sub(self, rhs: Self) -> Self::Output {
193        Self(self.0 - rhs.0)
194    }
195}
196impl ops::SubAssign for AngleGradian {
197    fn sub_assign(&mut self, rhs: Self) {
198        self.0 -= rhs.0;
199    }
200}
201impl ops::Neg for AngleGradian {
202    type Output = Self;
203
204    fn neg(self) -> Self::Output {
205        Self(-self.0)
206    }
207}
208impl Ord for AngleGradian {
209    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
210        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
211    }
212}
213impl PartialOrd for AngleGradian {
214    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
215        Some(self.cmp(other))
216    }
217}
218
219impl PartialEq for AngleGradian {
220    fn eq(&self, other: &Self) -> bool {
221        about_eq(self.0, other.0, EQ_GRANULARITY_100)
222    }
223}
224impl Eq for AngleGradian {}
225impl std::hash::Hash for AngleGradian {
226    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
227        about_eq_hash(self.0, EQ_GRANULARITY_100, state);
228    }
229}
230impl From<AngleRadian> for AngleGradian {
231    fn from(rad: AngleRadian) -> Self {
232        AngleGradian(rad.0 * 200.0 / PI)
233    }
234}
235impl From<AngleDegree> for AngleGradian {
236    fn from(deg: AngleDegree) -> Self {
237        AngleGradian(deg.0 * 10.0 / 9.0)
238    }
239}
240impl From<AngleTurn> for AngleGradian {
241    fn from(turn: AngleTurn) -> Self {
242        AngleGradian(turn.0 * 400.0)
243    }
244}
245impl fmt::Debug for AngleGradian {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        if f.alternate() {
248            f.debug_tuple("AngleGradian").field(&self.0).finish()
249        } else {
250            write!(f, "{}.grad()", self.0)
251        }
252    }
253}
254impl fmt::Display for AngleGradian {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        write!(f, "{} gon", self.0)
257    }
258}
259/// Parses `"##"`, `"## gon"` and `"##.grad()"` where `##` is a `f32`.
260impl std::str::FromStr for AngleGradian {
261    type Err = std::num::ParseFloatError;
262
263    fn from_str(s: &str) -> Result<Self, Self::Err> {
264        crate::parse_suffix(s, &[" gon", "gon", ".grad()"]).map(AngleGradian)
265    }
266}
267
268/// Angle in degrees.
269///
270/// See [`AngleUnits`] for more details.
271///
272/// # Equality
273///
274/// Equality is determined using [`about_eq`] with `0.001` granularity.
275#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
276#[serde(transparent)]
277pub struct AngleDegree(pub f32);
278impl AngleDegree {
279    /// Degrees in `[0.0 ..= 360.0]`.
280    pub fn modulo(self) -> Self {
281        AngleDegree(self.0.rem_euclid(360.0))
282    }
283
284    /// Linear interpolation.
285    pub fn lerp(self, to: Self, factor: Factor) -> Self {
286        Self(lerp(self.0, to.0, factor))
287    }
288
289    /// Spherical linear interpolation.
290    ///
291    /// Always uses the shortest path from `self` to `to`.
292    ///
293    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
294    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
295    /// normalized.
296    ///
297    /// [`lerp`]: Self::lerp
298    pub fn slerp(self, to: Self, factor: Factor) -> Self {
299        Self(slerp(self.0, to.0, 360.0, factor))
300    }
301}
302impl ops::Add for AngleDegree {
303    type Output = Self;
304
305    fn add(self, rhs: Self) -> Self::Output {
306        Self(self.0 + rhs.0)
307    }
308}
309impl ops::AddAssign for AngleDegree {
310    fn add_assign(&mut self, rhs: Self) {
311        self.0 += rhs.0;
312    }
313}
314impl ops::Sub for AngleDegree {
315    type Output = Self;
316
317    fn sub(self, rhs: Self) -> Self::Output {
318        Self(self.0 - rhs.0)
319    }
320}
321impl ops::SubAssign for AngleDegree {
322    fn sub_assign(&mut self, rhs: Self) {
323        self.0 -= rhs.0;
324    }
325}
326impl ops::Neg for AngleDegree {
327    type Output = Self;
328
329    fn neg(self) -> Self::Output {
330        Self(-self.0)
331    }
332}
333
334impl PartialEq for AngleDegree {
335    fn eq(&self, other: &Self) -> bool {
336        about_eq(self.0, other.0, EQ_GRANULARITY_100)
337    }
338}
339impl Eq for AngleDegree {}
340impl Ord for AngleDegree {
341    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
342        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
343    }
344}
345impl PartialOrd for AngleDegree {
346    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
347        Some(self.cmp(other))
348    }
349}
350impl std::hash::Hash for AngleDegree {
351    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
352        about_eq_hash(self.0, EQ_GRANULARITY_100, state);
353    }
354}
355impl From<AngleRadian> for AngleDegree {
356    fn from(rad: AngleRadian) -> Self {
357        AngleDegree(rad.0.to_degrees())
358    }
359}
360impl From<AngleGradian> for AngleDegree {
361    fn from(grad: AngleGradian) -> Self {
362        AngleDegree(grad.0 * 9.0 / 10.0)
363    }
364}
365impl From<AngleTurn> for AngleDegree {
366    fn from(turn: AngleTurn) -> Self {
367        AngleDegree(turn.0 * 360.0)
368    }
369}
370impl fmt::Debug for AngleDegree {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372        if f.alternate() {
373            f.debug_tuple("AngleDegree").field(&self.0).finish()
374        } else {
375            write!(f, "{}.deg()", self.0)
376        }
377    }
378}
379impl fmt::Display for AngleDegree {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        write!(f, "{}º", self.0)
382    }
383}
384/// Parses `"##"`, `"##º"` and `"##.deg()"` where `##` is a `f32`.
385impl std::str::FromStr for AngleDegree {
386    type Err = std::num::ParseFloatError;
387
388    fn from_str(s: &str) -> Result<Self, Self::Err> {
389        crate::parse_suffix(s, &["º", ".deg()"]).map(AngleDegree)
390    }
391}
392
393/// Angle in turns (complete rotations).
394///
395/// See [`AngleUnits`] for more details.
396///
397/// # Equality
398///
399/// Equality is determined using [`about_eq`] with `0.00001` granularity.
400#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
401#[serde(transparent)]
402pub struct AngleTurn(pub f32);
403impl AngleTurn {
404    /// Turns in `[0.0 ..= 1.0]`.
405    pub fn modulo(self) -> Self {
406        AngleTurn(self.0.rem_euclid(1.0))
407    }
408
409    /// Linear interpolation.
410    pub fn lerp(self, to: Self, factor: Factor) -> Self {
411        Self(lerp(self.0, to.0, factor))
412    }
413
414    /// Spherical linear interpolation.
415    ///
416    /// Always uses the shortest path from `self` to `to`.
417    ///
418    /// The [`lerp`] linear interpolation always covers the numeric range between angles, so a transition from 358º to 1º
419    /// iterates over almost a full counterclockwise turn to reach the final value, `slerp` simply goes from 358º to 361º modulo
420    /// normalized.
421    ///
422    /// [`lerp`]: Self::lerp
423    pub fn slerp(self, to: Self, factor: Factor) -> Self {
424        Self(slerp(self.0, to.0, 1.0, factor))
425    }
426}
427impl ops::Add for AngleTurn {
428    type Output = Self;
429
430    fn add(self, rhs: Self) -> Self::Output {
431        Self(self.0 + rhs.0)
432    }
433}
434impl ops::AddAssign for AngleTurn {
435    fn add_assign(&mut self, rhs: Self) {
436        self.0 += rhs.0;
437    }
438}
439impl ops::Sub for AngleTurn {
440    type Output = Self;
441
442    fn sub(self, rhs: Self) -> Self::Output {
443        Self(self.0 - rhs.0)
444    }
445}
446impl ops::SubAssign for AngleTurn {
447    fn sub_assign(&mut self, rhs: Self) {
448        self.0 -= rhs.0;
449    }
450}
451impl ops::Neg for AngleTurn {
452    type Output = Self;
453
454    fn neg(self) -> Self::Output {
455        Self(-self.0)
456    }
457}
458
459impl fmt::Debug for AngleTurn {
460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461        if f.alternate() {
462            f.debug_tuple("AngleTurn").field(&self.0).finish()
463        } else {
464            write!(f, "{}.turn()", self.0)
465        }
466    }
467}
468impl fmt::Display for AngleTurn {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        if (self.0 - 1.0).abs() < 0.0001 {
471            write!(f, "1 turn")
472        } else {
473            write!(f, "{} turns", self.0)
474        }
475    }
476}
477/// Parses `"##"`, `"## turn"`, `"## turns"` and `"##.turn()"` where `##` is a `f32`.
478impl std::str::FromStr for AngleTurn {
479    type Err = std::num::ParseFloatError;
480
481    fn from_str(s: &str) -> Result<Self, Self::Err> {
482        crate::parse_suffix(s, &[" turn", " turns", "turn", "turns", ".turn()"]).map(AngleTurn)
483    }
484}
485impl PartialEq for AngleTurn {
486    fn eq(&self, other: &Self) -> bool {
487        about_eq(self.0, other.0, EQ_GRANULARITY)
488    }
489}
490impl Eq for AngleTurn {}
491impl Ord for AngleTurn {
492    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
493        about_eq_ord(self.0, other.0, EQ_GRANULARITY)
494    }
495}
496impl PartialOrd for AngleTurn {
497    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
498        Some(self.cmp(other))
499    }
500}
501impl std::hash::Hash for AngleTurn {
502    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
503        about_eq_hash(self.0, EQ_GRANULARITY, state);
504    }
505}
506
507impl From<AngleRadian> for AngleTurn {
508    fn from(rad: AngleRadian) -> Self {
509        AngleTurn(rad.0 / TAU)
510    }
511}
512impl From<AngleGradian> for AngleTurn {
513    fn from(grad: AngleGradian) -> Self {
514        AngleTurn(grad.0 / 400.0)
515    }
516}
517impl From<AngleDegree> for AngleTurn {
518    fn from(deg: AngleDegree) -> Self {
519        AngleTurn(deg.0 / 360.0)
520    }
521}
522
523/// Extension methods for initializing angle units.
524///
525/// This trait is implemented for [`f32`] and [`u32`] allowing initialization of angle unit types using the `<number>.<unit>()` syntax.
526///
527/// # Examples
528///
529/// ```
530/// # use zng_unit::*;
531/// let radians = 6.28318.rad();
532/// let gradians = 400.grad();
533/// let degrees = 360.deg();
534/// let turns = 1.turn();
535/// ```
536pub trait AngleUnits {
537    /// Radians
538    fn rad(self) -> AngleRadian;
539    /// Gradians
540    fn grad(self) -> AngleGradian;
541    /// Degrees
542    fn deg(self) -> AngleDegree;
543    /// Turns
544    fn turn(self) -> AngleTurn;
545}
546impl AngleUnits for f32 {
547    fn rad(self) -> AngleRadian {
548        AngleRadian(self)
549    }
550
551    fn grad(self) -> AngleGradian {
552        AngleGradian(self)
553    }
554
555    fn deg(self) -> AngleDegree {
556        AngleDegree(self)
557    }
558
559    fn turn(self) -> AngleTurn {
560        AngleTurn(self)
561    }
562}
563impl AngleUnits for i32 {
564    fn rad(self) -> AngleRadian {
565        AngleRadian(self as f32)
566    }
567
568    fn grad(self) -> AngleGradian {
569        AngleGradian(self as f32)
570    }
571
572    fn deg(self) -> AngleDegree {
573        AngleDegree(self as f32)
574    }
575
576    fn turn(self) -> AngleTurn {
577        AngleTurn(self as f32)
578    }
579}
580
581fn lerp(from: f32, to: f32, factor: Factor) -> f32 {
582    from + (to - from) * factor.0
583}
584
585fn slerp(from: f32, to: f32, turn: f32, factor: Factor) -> f32 {
586    let angle_to = {
587        let d = (to - from) % turn;
588        2.0 * d % turn - d
589    };
590    from + angle_to * factor.0
591}