Skip to main content

space_units/quantities/
angular.rs

1use super::DisplayWithUnit;
2
3// -------------------------
4// Angle
5// -------------------------
6
7/// A plane angle, stored canonically in radians.
8///
9/// # Construction
10///
11/// ```
12/// use space_units::Angle;
13///
14/// let a = Angle::from_deg(90.0);
15/// let b = Angle::from_rad(std::f64::consts::FRAC_PI_2);
16/// ```
17///
18/// With the `NumericExt` trait in scope:
19///
20/// ```
21/// use space_units::{Angle, NumericExt as _};
22///
23/// let a = 90.0.deg();
24/// let b = (std::f64::consts::FRAC_PI_2).rad();
25/// ```
26///
27/// # Typed arithmetic
28///
29/// | Expression | Result |
30/// |---|---|
31/// | [`Angle`] / [`Time`](crate::Time) | [`AngularVelocity`] |
32#[must_use]
33#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
34pub struct Angle(pub(crate) f64);
35
36/// Display and conversion units for [`Angle`].
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum AngleUnit {
39    /// Radians (rad) -- the canonical storage unit.
40    Radian,
41    /// Degrees (deg).
42    Degree,
43    /// Arc-minutes (arcmin), 1/60 of a degree.
44    ArcMinute,
45    /// Arc-seconds (arcsec), 1/3600 of a degree.
46    ArcSecond,
47    /// Milli-arc-seconds (mas), 1/1000 of an arc-second.
48    MilliArcSecond,
49    /// Full revolutions (rev), equal to 2*pi* radians.
50    Revolution,
51    /// Milliradians (mrad), 1/1000 of a radian.
52    MilliRadian,
53    /// Gradians (grad), 1/400 of a full turn.
54    Grad,
55    /// Hour angles, 1/24 of a full turn (used in astronomy).
56    HourAngle,
57}
58
59impl AngleUnit {
60    const fn symbol(self) -> &'static str {
61        match self {
62            Self::Radian => "rad",
63            Self::Degree => "deg",
64            Self::ArcMinute => "arcmin",
65            Self::ArcSecond => "arcsec",
66            Self::MilliArcSecond => "mas",
67            Self::Revolution => "rev",
68            Self::MilliRadian => "mrad",
69            Self::Grad => "grad",
70            Self::HourAngle => "hour_angle",
71        }
72    }
73
74    const fn radians_per_unit(self) -> f64 {
75        match self {
76            Self::Radian => 1.0,
77            Self::Degree => core::f64::consts::PI / 180.0,
78            Self::ArcMinute => core::f64::consts::PI / 10_800.0,
79            Self::ArcSecond => core::f64::consts::PI / 648_000.0,
80            Self::MilliArcSecond => core::f64::consts::PI / 648_000_000.0,
81            Self::Revolution => core::f64::consts::TAU,
82            Self::MilliRadian => 1e-3,
83            Self::Grad => core::f64::consts::PI / 200.0,
84            Self::HourAngle => core::f64::consts::PI / 12.0,
85        }
86    }
87}
88
89impl Angle {
90    /// Creates an angle from a value in radians.
91    pub const fn from_rad(val: f64) -> Self {
92        Self(val)
93    }
94
95    /// Creates an angle from a value in degrees.
96    pub const fn from_deg(val: f64) -> Self {
97        Self(val * (core::f64::consts::PI / 180.0))
98    }
99
100    /// Creates an angle from a value in full revolutions.
101    pub const fn from_rev(val: f64) -> Self {
102        Self(val * core::f64::consts::TAU)
103    }
104
105    /// Creates an angle from a value in arc-minutes.
106    pub const fn from_arcmin(val: f64) -> Self {
107        Self(val * (core::f64::consts::PI / 10_800.0))
108    }
109
110    /// Creates an angle from a value in arc-seconds.
111    pub const fn from_arcsec(val: f64) -> Self {
112        Self(val * (core::f64::consts::PI / 648_000.0))
113    }
114
115    /// Creates an angle from a value in milli-arc-seconds.
116    pub const fn from_mas(val: f64) -> Self {
117        Self(val * (core::f64::consts::PI / 648_000_000.0))
118    }
119
120    /// Creates an angle from a value in milliradians.
121    pub const fn from_mrad(val: f64) -> Self {
122        Self(val * 1e-3)
123    }
124
125    /// Creates an angle from a value in gradians.
126    pub const fn from_grad(val: f64) -> Self {
127        Self(val * (core::f64::consts::PI / 200.0))
128    }
129
130    /// Creates an angle from a value in hour angles.
131    pub const fn from_hour_angle(val: f64) -> Self {
132        Self(val * (core::f64::consts::PI / 12.0))
133    }
134
135    /// Returns the angle in radians.
136    pub const fn in_rad(self) -> f64 {
137        self.0
138    }
139
140    /// Returns the angle in degrees.
141    pub const fn in_deg(self) -> f64 {
142        self.0 / (core::f64::consts::PI / 180.0)
143    }
144
145    /// Returns the angle in full revolutions.
146    pub const fn in_rev(self) -> f64 {
147        self.0 / core::f64::consts::TAU
148    }
149
150    /// Returns the angle in arc-minutes.
151    pub const fn in_arcmin(self) -> f64 {
152        self.0 / (core::f64::consts::PI / 10_800.0)
153    }
154
155    /// Returns the angle in arc-seconds.
156    pub const fn in_arcsec(self) -> f64 {
157        self.0 / (core::f64::consts::PI / 648_000.0)
158    }
159
160    /// Returns the angle in milli-arc-seconds.
161    pub const fn in_mas(self) -> f64 {
162        self.0 / (core::f64::consts::PI / 648_000_000.0)
163    }
164
165    /// Returns the angle in milliradians.
166    pub const fn in_mrad(self) -> f64 {
167        self.0 / 1e-3
168    }
169
170    /// Returns the angle in gradians.
171    pub const fn in_grad(self) -> f64 {
172        self.0 / (core::f64::consts::PI / 200.0)
173    }
174
175    /// Returns the angle in hour angles.
176    pub const fn in_hour_angle(self) -> f64 {
177        self.0 / (core::f64::consts::PI / 12.0)
178    }
179
180    /// Returns the angle in the specified [`AngleUnit`].
181    pub fn in_unit(self, unit: AngleUnit) -> f64 {
182        self.0 / unit.radians_per_unit()
183    }
184
185    /// Returns a display wrapper that formats the angle in the specified unit.
186    pub fn display_as(self, unit: AngleUnit) -> DisplayWithUnit {
187        DisplayWithUnit {
188            value: self.in_unit(unit),
189            symbol: unit.symbol(),
190        }
191    }
192
193    /// Normalize into the range `[0, 2π)`.
194    pub fn normalize(self) -> Self {
195        let tau = core::f64::consts::TAU;
196        let mut v = self.0 % tau;
197        if v < 0.0 {
198            v += tau;
199        }
200        Self(v)
201    }
202
203    /// Returns the absolute value of the angle.
204    pub fn abs(self) -> Self {
205        Self(self.0.abs())
206    }
207
208    /// Returns the sine of the angle.
209    #[cfg(feature = "std")]
210    pub fn sin(self) -> f64 {
211        self.0.sin()
212    }
213
214    /// Returns the cosine of the angle.
215    #[cfg(feature = "std")]
216    pub fn cos(self) -> f64 {
217        self.0.cos()
218    }
219
220    /// Returns the tangent of the angle.
221    #[cfg(feature = "std")]
222    pub fn tan(self) -> f64 {
223        self.0.tan()
224    }
225
226    /// Returns the angle whose sine is `val` (inverse sine).
227    #[cfg(feature = "std")]
228    pub fn asin(val: f64) -> Self {
229        Self(val.asin())
230    }
231
232    /// Returns the angle whose cosine is `val` (inverse cosine).
233    #[cfg(feature = "std")]
234    pub fn acos(val: f64) -> Self {
235        Self(val.acos())
236    }
237
238    /// Returns the four-quadrant arc-tangent of `y / x`.
239    #[cfg(feature = "std")]
240    pub fn atan2(y: f64, x: f64) -> Self {
241        Self(y.atan2(x))
242    }
243}
244
245impl_quantity_display!(Angle, "rad");
246
247impl_common_ops!(Angle);
248
249// -------------------------
250// Angular velocity
251// -------------------------
252
253/// An angular velocity (rate of rotation), stored canonically in rad/s.
254///
255/// # Construction
256///
257/// ```
258/// use space_units::AngularVelocity;
259///
260/// let w = AngularVelocity::from_rpm(3600.0);
261/// let w2 = AngularVelocity::from_radps(1.0);
262/// ```
263///
264/// # Typed arithmetic
265///
266/// | Expression | Result |
267/// |---|---|
268/// | [`AngularVelocity`] * [`Time`](crate::Time) | [`Angle`] |
269/// | [`AngularVelocity`] / [`Time`](crate::Time) | [`AngularAcceleration`] |
270#[must_use]
271#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
272pub struct AngularVelocity(pub(crate) f64);
273
274/// Display and conversion units for [`AngularVelocity`].
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum AngularVelocityUnit {
277    /// Radians per second (rad/s) -- the canonical storage unit.
278    RadPerS,
279    /// Degrees per second (deg/s).
280    DegPerS,
281    /// Revolutions per minute (rpm).
282    Rpm,
283    /// Revolutions per second (rev/s).
284    RevPerS,
285}
286
287impl AngularVelocityUnit {
288    const fn symbol(self) -> &'static str {
289        match self {
290            Self::RadPerS => "rad/s",
291            Self::DegPerS => "deg/s",
292            Self::Rpm => "rpm",
293            Self::RevPerS => "rev/s",
294        }
295    }
296
297    const fn radps_per_unit(self) -> f64 {
298        match self {
299            Self::RadPerS => 1.0,
300            Self::DegPerS => core::f64::consts::PI / 180.0,
301            Self::Rpm => core::f64::consts::TAU / 60.0,
302            Self::RevPerS => core::f64::consts::TAU,
303        }
304    }
305}
306
307impl AngularVelocity {
308    /// Creates an angular velocity from a value in radians per second.
309    pub const fn from_radps(val: f64) -> Self {
310        Self(val)
311    }
312
313    /// Creates an angular velocity from a value in degrees per second.
314    pub const fn from_degps(val: f64) -> Self {
315        Self(val * (core::f64::consts::PI / 180.0))
316    }
317
318    /// Creates an angular velocity from a value in revolutions per minute.
319    pub const fn from_rpm(val: f64) -> Self {
320        Self(val * (core::f64::consts::TAU / 60.0))
321    }
322
323    /// Creates an angular velocity from a value in revolutions per second.
324    pub const fn from_revps(val: f64) -> Self {
325        Self(val * core::f64::consts::TAU)
326    }
327
328    /// Returns the angular velocity in radians per second.
329    pub const fn in_radps(self) -> f64 {
330        self.0
331    }
332
333    /// Returns the angular velocity in degrees per second.
334    pub const fn in_degps(self) -> f64 {
335        self.0 / (core::f64::consts::PI / 180.0)
336    }
337
338    /// Returns the angular velocity in revolutions per minute.
339    pub const fn in_rpm(self) -> f64 {
340        self.0 / (core::f64::consts::TAU / 60.0)
341    }
342
343    /// Returns the angular velocity in revolutions per second.
344    pub const fn in_revps(self) -> f64 {
345        self.0 / core::f64::consts::TAU
346    }
347
348    /// Returns the angular velocity in the specified [`AngularVelocityUnit`].
349    pub fn in_unit(self, unit: AngularVelocityUnit) -> f64 {
350        self.0 / unit.radps_per_unit()
351    }
352
353    /// Returns a display wrapper that formats the angular velocity in the specified unit.
354    pub fn display_as(self, unit: AngularVelocityUnit) -> DisplayWithUnit {
355        DisplayWithUnit {
356            value: self.in_unit(unit),
357            symbol: unit.symbol(),
358        }
359    }
360
361    /// Returns the absolute value of the angular velocity.
362    pub fn abs(self) -> Self {
363        Self(self.0.abs())
364    }
365}
366
367impl_quantity_display!(AngularVelocity, "rad/s");
368
369impl_common_ops!(AngularVelocity);
370
371// -------------------------
372// Angular acceleration
373// -------------------------
374
375/// An angular acceleration, stored canonically in rad/s².
376///
377/// # Construction
378///
379/// ```
380/// use space_units::AngularAcceleration;
381///
382/// let alpha = AngularAcceleration::from_radps2(0.5);
383/// let alpha2 = AngularAcceleration::from_degps2(30.0);
384/// ```
385///
386/// # Typed arithmetic
387///
388/// | Expression | Result |
389/// |---|---|
390/// | [`MomentOfInertia`] * [`AngularAcceleration`] | [`Torque`] |
391#[must_use]
392#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
393pub struct AngularAcceleration(pub(crate) f64);
394
395/// Display and conversion units for [`AngularAcceleration`].
396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397pub enum AngularAccelerationUnit {
398    /// Radians per second squared (rad/s²) -- the canonical storage unit.
399    RadPerS2,
400    /// Degrees per second squared (deg/s²).
401    DegPerS2,
402    /// Revolutions per second squared (rev/s²).
403    RevPerS2,
404}
405
406impl AngularAccelerationUnit {
407    const fn symbol(self) -> &'static str {
408        match self {
409            Self::RadPerS2 => "rad/s^2",
410            Self::DegPerS2 => "deg/s^2",
411            Self::RevPerS2 => "rev/s^2",
412        }
413    }
414
415    const fn radps2_per_unit(self) -> f64 {
416        match self {
417            Self::RadPerS2 => 1.0,
418            Self::DegPerS2 => core::f64::consts::PI / 180.0,
419            Self::RevPerS2 => core::f64::consts::TAU,
420        }
421    }
422}
423
424impl AngularAcceleration {
425    /// Creates an angular acceleration from a value in radians per second squared.
426    pub const fn from_radps2(val: f64) -> Self {
427        Self(val)
428    }
429
430    /// Creates an angular acceleration from a value in degrees per second squared.
431    pub const fn from_degps2(val: f64) -> Self {
432        Self(val * (core::f64::consts::PI / 180.0))
433    }
434
435    /// Creates an angular acceleration from a value in revolutions per second squared.
436    pub const fn from_revps2(val: f64) -> Self {
437        Self(val * core::f64::consts::TAU)
438    }
439
440    /// Returns the angular acceleration in radians per second squared.
441    pub const fn in_radps2(self) -> f64 {
442        self.0
443    }
444
445    /// Returns the angular acceleration in degrees per second squared.
446    pub const fn in_degps2(self) -> f64 {
447        self.0 / (core::f64::consts::PI / 180.0)
448    }
449
450    /// Returns the angular acceleration in revolutions per second squared.
451    pub const fn in_revps2(self) -> f64 {
452        self.0 / core::f64::consts::TAU
453    }
454
455    /// Returns the angular acceleration in the specified [`AngularAccelerationUnit`].
456    pub fn in_unit(self, unit: AngularAccelerationUnit) -> f64 {
457        self.0 / unit.radps2_per_unit()
458    }
459
460    /// Returns a display wrapper that formats the angular acceleration in the specified unit.
461    pub fn display_as(self, unit: AngularAccelerationUnit) -> DisplayWithUnit {
462        DisplayWithUnit {
463            value: self.in_unit(unit),
464            symbol: unit.symbol(),
465        }
466    }
467
468    /// Returns the absolute value of the angular acceleration.
469    pub fn abs(self) -> Self {
470        Self(self.0.abs())
471    }
472}
473
474impl_quantity_display!(AngularAcceleration, "rad/s²");
475
476impl_common_ops!(AngularAcceleration);
477
478// -------------------------
479// Torque
480// -------------------------
481
482/// A torque (moment of force), stored canonically in newton-metres (N*m).
483///
484/// # Construction
485///
486/// ```
487/// use space_units::Torque;
488///
489/// let t = Torque::from_nm(50.0);
490/// let t2 = Torque::from_lbf_ft(36.88);
491/// ```
492///
493/// # Typed arithmetic
494///
495/// | Expression | Result |
496/// |---|---|
497/// | [`Torque`] / [`AngularAcceleration`] | [`MomentOfInertia`] |
498/// | [`Torque`] * [`Time`](crate::Time) | [`AngularMomentum`] |
499#[must_use]
500#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
501pub struct Torque(pub(crate) f64);
502
503/// Display and conversion units for [`Torque`].
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum TorqueUnit {
506    /// Newton-metres (N*m) -- the canonical storage unit.
507    NewtonMeter,
508    /// Kilonewton-metres (kN*m).
509    KiloNewtonMeter,
510    /// Pound-force feet (lbf*ft).
511    PoundForceFoot,
512}
513
514impl TorqueUnit {
515    const fn symbol(self) -> &'static str {
516        match self {
517            Self::NewtonMeter => "N*m",
518            Self::KiloNewtonMeter => "kN*m",
519            Self::PoundForceFoot => "lbf*ft",
520        }
521    }
522}
523
524impl Torque {
525    /// Creates a torque from a value in newton-metres.
526    pub const fn from_nm(val: f64) -> Self {
527        Self(val)
528    }
529
530    /// Creates a torque from a value in kilonewton-metres.
531    pub const fn from_knm(val: f64) -> Self {
532        Self(val * 1_000.0)
533    }
534
535    /// Creates a torque from a value in pound-force feet.
536    pub const fn from_lbf_ft(val: f64) -> Self {
537        Self(val * 1.355_817_948_331_4)
538    }
539
540    /// Returns the torque in newton-metres.
541    pub const fn in_nm(self) -> f64 {
542        self.0
543    }
544
545    /// Returns the torque in kilonewton-metres.
546    pub const fn in_knm(self) -> f64 {
547        self.0 / 1_000.0
548    }
549
550    /// Returns the torque in pound-force feet.
551    pub const fn in_lbf_ft(self) -> f64 {
552        self.0 / 1.355_817_948_331_4
553    }
554
555    /// Returns a display wrapper that formats the torque in the specified unit.
556    pub const fn display_as(self, unit: TorqueUnit) -> DisplayWithUnit {
557        match unit {
558            TorqueUnit::NewtonMeter => DisplayWithUnit {
559                value: self.0,
560                symbol: unit.symbol(),
561            },
562            TorqueUnit::KiloNewtonMeter => DisplayWithUnit {
563                value: self.in_knm(),
564                symbol: unit.symbol(),
565            },
566            TorqueUnit::PoundForceFoot => DisplayWithUnit {
567                value: self.in_lbf_ft(),
568                symbol: unit.symbol(),
569            },
570        }
571    }
572
573    /// Returns the absolute value of the torque.
574    pub fn abs(self) -> Self {
575        Self(self.0.abs())
576    }
577}
578
579impl_quantity_display!(Torque, "N·m");
580
581impl_common_ops!(Torque);
582
583// -------------------------
584// Angular momentum
585// -------------------------
586
587/// An angular momentum, stored canonically in newton-metre-seconds (N*m*s).
588///
589/// # Construction
590///
591/// ```
592/// use space_units::AngularMomentum;
593///
594/// let l = AngularMomentum::from_n_m_s(42.0);
595/// ```
596#[must_use]
597#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
598pub struct AngularMomentum(pub(crate) f64);
599
600/// Display and conversion units for [`AngularMomentum`].
601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
602pub enum AngularMomentumUnit {
603    /// Newton-metre-seconds (N*m*s) -- the canonical storage unit.
604    NewtonMeterSecond,
605}
606
607impl AngularMomentumUnit {
608    const fn symbol(self) -> &'static str {
609        match self {
610            Self::NewtonMeterSecond => "N*m*s",
611        }
612    }
613}
614
615impl AngularMomentum {
616    /// Creates an angular momentum from a value in newton-metre-seconds.
617    pub const fn from_n_m_s(val: f64) -> Self {
618        Self(val)
619    }
620
621    /// Returns the angular momentum in newton-metre-seconds.
622    pub const fn in_n_m_s(self) -> f64 {
623        self.0
624    }
625
626    /// Returns a display wrapper that formats the angular momentum in the specified unit.
627    pub const fn display_as(self, unit: AngularMomentumUnit) -> DisplayWithUnit {
628        match unit {
629            AngularMomentumUnit::NewtonMeterSecond => DisplayWithUnit {
630                value: self.0,
631                symbol: unit.symbol(),
632            },
633        }
634    }
635
636    /// Returns the absolute value of the angular momentum.
637    pub fn abs(self) -> Self {
638        Self(self.0.abs())
639    }
640}
641
642impl_quantity_display!(AngularMomentum, "N·m·s");
643
644impl_common_ops!(AngularMomentum);
645
646// -------------------------
647// Moment of inertia
648// -------------------------
649
650/// A moment of inertia (rotational inertia), stored canonically in kg*m².
651///
652/// # Construction
653///
654/// ```
655/// use space_units::MomentOfInertia;
656///
657/// let i = MomentOfInertia::from_kgm2(12.5);
658/// let i2 = MomentOfInertia::from_slug_ft2(9.2);
659/// ```
660///
661/// # Typed arithmetic
662///
663/// | Expression | Result |
664/// |---|---|
665/// | [`MomentOfInertia`] * [`AngularAcceleration`] | [`Torque`] |
666#[must_use]
667#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
668pub struct MomentOfInertia(pub(crate) f64);
669
670/// Display and conversion units for [`MomentOfInertia`].
671#[derive(Debug, Clone, Copy, PartialEq, Eq)]
672pub enum MomentOfInertiaUnit {
673    /// Kilogram-metres squared (kg*m²) -- the canonical storage unit.
674    KgM2,
675    /// Gram-centimetres squared (g*cm²).
676    GramCentimeter2,
677    /// Slug-feet squared (slug*ft²).
678    SlugFoot2,
679}
680
681impl MomentOfInertiaUnit {
682    const fn symbol(self) -> &'static str {
683        match self {
684            Self::KgM2 => "kg*m^2",
685            Self::GramCentimeter2 => "g*cm^2",
686            Self::SlugFoot2 => "slug*ft^2",
687        }
688    }
689}
690
691impl MomentOfInertia {
692    /// Creates a moment of inertia from a value in kilogram-metres squared.
693    pub const fn from_kgm2(val: f64) -> Self {
694        Self(val)
695    }
696
697    /// Creates a moment of inertia from a value in gram-centimetres squared.
698    pub const fn from_gcm2(val: f64) -> Self {
699        Self(val * 1e-7)
700    }
701
702    /// Creates a moment of inertia from a value in slug-feet squared.
703    pub const fn from_slug_ft2(val: f64) -> Self {
704        Self(val * 1.355_817_948_331_4)
705    }
706
707    /// Returns the moment of inertia in kilogram-metres squared.
708    pub const fn in_kgm2(self) -> f64 {
709        self.0
710    }
711
712    /// Returns the moment of inertia in gram-centimetres squared.
713    pub const fn in_gcm2(self) -> f64 {
714        self.0 / 1e-7
715    }
716
717    /// Returns the moment of inertia in slug-feet squared.
718    pub const fn in_slug_ft2(self) -> f64 {
719        self.0 / 1.355_817_948_331_4
720    }
721
722    /// Returns a display wrapper that formats the moment of inertia in the specified unit.
723    pub const fn display_as(self, unit: MomentOfInertiaUnit) -> DisplayWithUnit {
724        match unit {
725            MomentOfInertiaUnit::KgM2 => DisplayWithUnit {
726                value: self.0,
727                symbol: unit.symbol(),
728            },
729            MomentOfInertiaUnit::GramCentimeter2 => DisplayWithUnit {
730                value: self.in_gcm2(),
731                symbol: unit.symbol(),
732            },
733            MomentOfInertiaUnit::SlugFoot2 => DisplayWithUnit {
734                value: self.in_slug_ft2(),
735                symbol: unit.symbol(),
736            },
737        }
738    }
739
740    /// Returns the absolute value of the moment of inertia.
741    pub fn abs(self) -> Self {
742        Self(self.0.abs())
743    }
744}
745
746impl_quantity_display!(MomentOfInertia, "kg·m²");
747
748impl_common_ops!(MomentOfInertia);
749
750// -------------------------
751// Solid angle
752// -------------------------
753
754/// A solid angle (two-dimensional angular span), stored canonically in steradians (sr).
755///
756/// # Construction
757///
758/// ```
759/// use space_units::SolidAngle;
760///
761/// let omega = SolidAngle::from_sr(2.0);
762/// let omega2 = SolidAngle::from_deg2(100.0);
763/// let full_sphere = SolidAngle::from_spat(1.0);
764/// ```
765#[must_use]
766#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
767pub struct SolidAngle(pub(crate) f64);
768
769/// Display and conversion units for [`SolidAngle`].
770#[derive(Debug, Clone, Copy, PartialEq, Eq)]
771pub enum SolidAngleUnit {
772    /// Steradians (sr) -- the canonical storage unit.
773    Steradian,
774    /// Square degrees (deg²).
775    SquareDegree,
776    /// Spats (the full sphere, 4*pi* sr).
777    Spat,
778}
779
780impl SolidAngleUnit {
781    const fn symbol(self) -> &'static str {
782        match self {
783            Self::Steradian => "sr",
784            Self::SquareDegree => "deg^2",
785            Self::Spat => "spat",
786        }
787    }
788
789    const fn sr_per_unit(self) -> f64 {
790        match self {
791            Self::Steradian => 1.0,
792            // (PI/180)^2
793            Self::SquareDegree => (core::f64::consts::PI / 180.0) * (core::f64::consts::PI / 180.0),
794            Self::Spat => 4.0 * core::f64::consts::PI,
795        }
796    }
797}
798
799impl SolidAngle {
800    /// Creates a solid angle from a value in steradians.
801    pub const fn from_sr(val: f64) -> Self {
802        Self(val)
803    }
804
805    /// Creates a solid angle from a value in square degrees.
806    pub const fn from_deg2(val: f64) -> Self {
807        Self(val * (core::f64::consts::PI / 180.0) * (core::f64::consts::PI / 180.0))
808    }
809
810    /// Creates a solid angle from a value in spats (full spheres).
811    pub const fn from_spat(val: f64) -> Self {
812        Self(val * 4.0 * core::f64::consts::PI)
813    }
814
815    /// Returns the solid angle in steradians.
816    pub const fn in_sr(self) -> f64 {
817        self.0
818    }
819
820    /// Returns the solid angle in square degrees.
821    pub const fn in_deg2(self) -> f64 {
822        self.0 / ((core::f64::consts::PI / 180.0) * (core::f64::consts::PI / 180.0))
823    }
824
825    /// Returns the solid angle in spats (full spheres).
826    pub const fn in_spat(self) -> f64 {
827        self.0 / (4.0 * core::f64::consts::PI)
828    }
829
830    /// Returns the solid angle in the specified [`SolidAngleUnit`].
831    pub fn in_unit(self, unit: SolidAngleUnit) -> f64 {
832        self.0 / unit.sr_per_unit()
833    }
834
835    /// Returns a display wrapper that formats the solid angle in the specified unit.
836    pub fn display_as(self, unit: SolidAngleUnit) -> DisplayWithUnit {
837        DisplayWithUnit {
838            value: self.in_unit(unit),
839            symbol: unit.symbol(),
840        }
841    }
842
843    /// Returns the absolute value of the solid angle.
844    pub fn abs(self) -> Self {
845        Self(self.0.abs())
846    }
847}
848
849impl_quantity_display!(SolidAngle, "sr");
850
851impl_common_ops!(SolidAngle);