use crate::Measurement;
use std::f64::consts::PI;
#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Angle {
radians: f64,
}
impl Angle {
pub const ZERO: Angle = Angle { radians: 0.0 };
pub const QUARTER_CIRCLE: Angle = Angle { radians: PI / 2.0 };
pub const NEG_QUARTER_CIRCLE: Angle = Angle { radians: -PI / 2.0 };
pub const HALF_CIRCLE: Angle = Angle { radians: PI };
pub const NEG_HALF_CIRCLE: Angle = Angle { radians: -PI };
pub const FULL_CIRCLE: Angle = Angle { radians: 2.0 * PI };
pub(crate) const DBL_EPSILON: Angle = Angle {
radians: f64::EPSILON,
};
pub fn as_degrees(&self) -> f64 {
self.radians.to_degrees()
}
#[inline]
pub fn as_radians(&self) -> f64 {
self.radians
}
pub fn from_degrees(degrees: f64) -> Self {
Angle {
radians: degrees.to_radians(),
}
}
pub const fn from_radians(radians: f64) -> Self {
Angle { radians }
}
pub fn abs(&self) -> Self {
Angle {
radians: self.radians.abs(),
}
}
pub fn normalised(&self) -> Self {
self.normalised_to(Self::FULL_CIRCLE)
}
pub fn normalised_to(&self, max: Angle) -> Angle {
if self.radians >= 0.0 && self.radians < max.radians {
*self
} else {
let res = self.radians % max.radians;
if res < 0.0 {
Self::from_radians(res + max.radians)
} else {
Self::from_radians(res)
}
}
}
pub fn round_d5(&self) -> Self {
let d5 = (self.as_degrees() * 1e5).round() / 1e5;
Self::from_degrees(d5)
}
pub fn round_d6(&self) -> Self {
let d6 = (self.as_degrees() * 1e6).round() / 1e6;
Self::from_degrees(d6)
}
pub fn round_d7(&self) -> Self {
let d7 = (self.as_degrees() * 1e7).round() / 1e7;
Self::from_degrees(d7)
}
}
impl Measurement for Angle {
fn from_default_unit(amount: f64) -> Self {
Angle::from_radians(amount)
}
#[inline]
fn as_default_unit(&self) -> f64 {
self.as_radians()
}
}
impl_measurement! { Angle }
#[cfg(feature = "uom")]
impl From<uom::si::f64::Angle> for Angle {
fn from(value: uom::si::f64::Angle) -> Self {
Self::from_radians(value.get::<uom::si::angle::radian>())
}
}
#[cfg(feature = "uom")]
impl From<Angle> for uom::si::f64::Angle {
fn from(value: Angle) -> Self {
Self::new::<uom::si::angle::radian>(value.as_radians())
}
}
#[cfg(test)]
mod tests {
use std::f64::consts::PI;
use crate::Angle;
#[test]
fn conversions() {
assert_eq!(PI, Angle::from_degrees(180.0).as_radians());
assert_eq!(180.0, Angle::from_radians(PI).as_degrees());
}
#[test]
fn std_ops() {
assert_eq!(Angle::from_degrees(2.0), 2.0 * Angle::from_degrees(1.0));
assert_eq!(
Angle::from_degrees(2.0),
Angle::from_degrees(1.0) + Angle::from_degrees(1.0)
);
assert_eq!(
Angle::from_degrees(0.0),
Angle::from_degrees(1.0) - Angle::from_degrees(1.0)
);
}
#[test]
fn normalised() {
assert_eq!(
Angle::from_degrees(359.0),
Angle::from_degrees(-361.0).normalised()
);
assert_eq!(
Angle::from_degrees(358.0),
Angle::from_degrees(-2.0).normalised()
);
assert_eq!(
Angle::from_degrees(154.0),
Angle::from_degrees(154.0).normalised()
);
assert_eq!(Angle::ZERO, Angle::from_degrees(360.0).normalised());
}
#[test]
fn normalised_to() {
assert_eq!(
Angle::from_degrees(179.0),
Angle::from_degrees(-181.0)
.normalised_to(Angle::HALF_CIRCLE)
.round_d7()
);
assert_eq!(
Angle::from_degrees(1.0),
Angle::from_degrees(181.0)
.normalised_to(Angle::HALF_CIRCLE)
.round_d7()
);
assert_eq!(
Angle::from_degrees(154.0),
Angle::from_degrees(154.0).normalised_to(Angle::HALF_CIRCLE)
);
assert_eq!(
Angle::ZERO,
Angle::from_degrees(180.0).normalised_to(Angle::HALF_CIRCLE)
);
}
#[cfg(feature = "uom")]
#[test]
fn uom() {
let angle = Angle::from_radians(1.0);
let uom = uom::si::f64::Angle::from(angle);
let roundtrip = Angle::from(uom);
assert_eq!(angle, roundtrip);
}
}