use std::str::FromStr;
use num_traits::{AsPrimitive, Float};
use rand::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler};
use rand::Rng;
#[derive(
Debug, Clone, Default, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize,
)]
pub struct Angle(pub f64);
impl Angle {
#[must_use]
pub fn from_rad(rad: f64) -> Self {
Self(rad)
}
#[must_use]
pub fn from_deg(deg: f64) -> Self {
Self(deg.to_radians())
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn rad<F: Float>(&self) -> F {
F::from(self.0).unwrap()
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn deg<F: Float>(&self) -> F {
F::from(self.0.to_degrees()).unwrap()
}
}
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum AngleError {
#[error("Could not parse number: {0}")]
FloatParseError(#[from] std::num::ParseFloatError),
#[error("Could not parse unit: {0}")]
UnitParseError(String),
}
impl FromStr for Angle {
type Err = AngleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let number_str = s.trim_end_matches(|c: char| c.is_alphabetic() || c == '°');
let unit_str = &s[number_str.len()..];
let number_str = number_str.trim();
let angle = number_str.parse::<f64>()?;
match unit_str {
"rad" | "r" => Ok(Self::from_rad(angle)),
"deg" | "d" | "°" | "" => Ok(Self::from_deg(angle)),
_ => Err(AngleError::UnitParseError(unit_str.to_string())),
}
}
}
impl std::ops::Add<Angle> for Angle {
type Output = Self;
fn add(self, rhs: Angle) -> Self::Output {
Self::from_rad(self.0 + rhs.0)
}
}
impl std::ops::Sub<Angle> for Angle {
type Output = Self;
fn sub(self, rhs: Angle) -> Self::Output {
Self::from_rad(self.0 - rhs.0)
}
}
impl std::ops::Neg for Angle {
type Output = Self;
fn neg(self) -> Self::Output {
Self::from_rad(-self.0)
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Mul<F> for Angle {
type Output = Self;
fn mul(self, rhs: F) -> Self::Output {
Self::from_rad(self.0 * rhs.as_())
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Div<F> for Angle {
type Output = Self;
fn div(self, rhs: F) -> Self::Output {
Self::from_rad(self.0 / rhs.as_())
}
}
macro_rules! angle_trait_impl {
($t:ty) => {
impl From<Angle> for $t {
fn from(angle: Angle) -> Self {
angle.rad()
}
}
impl From<&'_ Angle> for $t {
fn from(angle: &'_ Angle) -> Self {
angle.rad()
}
}
impl std::ops::Mul<Angle> for $t {
type Output = Angle;
fn mul(self, rhs: Angle) -> Self::Output {
rhs.mul(self)
}
}
};
}
angle_trait_impl!(f32);
angle_trait_impl!(f64);
pub struct AngleSampler(rand::distributions::uniform::UniformFloat<f64>);
impl UniformSampler for crate::AngleSampler {
type X = Angle;
fn new<B1, B2>(low: B1, high: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
Self(rand::distributions::uniform::UniformFloat::new(
low.borrow().0,
high.borrow().0,
))
}
fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
Self(rand::distributions::uniform::UniformFloat::new_inclusive(
low.borrow().0,
high.borrow().0,
))
}
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
Self::X::from_rad(self.0.sample(rng))
}
}
impl SampleUniform for Angle {
type Sampler = crate::AngleSampler;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_angle_from_str() {
assert_eq!("0".parse::<Angle>().unwrap(), Angle(0.0));
assert_eq!("0.0".parse::<Angle>().unwrap(), Angle(0.0));
assert_eq!("1.0rad".parse::<Angle>().unwrap(), Angle(1.0));
assert_eq!("1.0r".parse::<Angle>().unwrap(), Angle(1.0));
assert_eq!("1.0deg".parse::<Angle>().unwrap(), Angle::from_deg(1.0));
assert_eq!("1.0d".parse::<Angle>().unwrap(), Angle::from_deg(1.0));
assert_eq!("1.0°".parse::<Angle>().unwrap(), Angle::from_deg(1.0));
assert_eq!("1.0".parse::<Angle>().unwrap(), Angle::from_deg(1.0));
assert_eq!(
"1.0hello".parse::<Angle>(),
Err(AngleError::UnitParseError("hello".to_string()))
);
}
}