xuko 0.10.0

Rust utility library
Documentation
//! Mathematics

use num_traits::{Float, Num};

/// Return the value of π for `T`
pub fn pi<T: Float>() -> T {
    T::from(3.141592653589).unwrap()
}

/// Return the value of τ for `T`
pub fn tau<T: Float>() -> T {
    T::from(2.0).unwrap() * pi()
}

/// Return the value φ for `T`
pub fn phi<T: Float>() -> T {
    T::from(1.618033988).unwrap()
}

/// Generic angle type, either degrees or radians
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Angle<T: Float> {
    /// Degrees
    Deg(T),

    /// Radians
    Rad(T),
}

impl<T: Float> Angle<T> {
    /// Return the [`Angle`] as degrees
    pub fn degrees(self) -> T {
        match self {
            Self::Deg(d) => d,
            Self::Rad(r) => r.to_degrees(),
        }
    }

    /// Return the [`Angle`] as radians
    pub fn radians(self) -> T {
        match self {
            Self::Deg(d) => d.to_radians(),
            Self::Rad(r) => r,
        }
    }

    /// Add `x` to the angle, wrapping if needed.
    pub fn add(&mut self, x: T) {
        match self {
            Self::Deg(n) => *n = (*n + x) % Self::degrees_360().degrees(),
            Self::Rad(n) => *n = (*n + x) % Self::full_radians().radians(),
        }
    }

    /// Subtract `x` from the angle, wrapping if needed.
    pub fn sub(&mut self, x: T) {
        self.add(-x);
    }

    /// Half rotation of radians (π)
    pub fn half_radians() -> Self {
        Self::Rad(pi())
    }

    /// Full rotation of radians (τ)
    pub fn full_radians() -> Self {
        Self::Rad(tau())
    }

    /// 180 degrees
    pub fn degrees_180() -> Self {
        Self::Deg(T::from(180.0).unwrap())
    }

    /// 360 degrees
    pub fn degrees_360() -> Self {
        Self::Deg(T::from(360.0).unwrap())
    }
}

pub use Angle::{Deg, Rad};

/// Linear interpolation
pub fn lerp<T: Clone + Float>(a: T, b: T, t: T) -> T {
    (T::one() - t) * a + t * b
}

/// Calculate the `n`th factorial
pub fn fact<T: Clone + Num + PartialOrd>(n: T) -> T {
    let mut x = n.clone();
    let mut accum = T::one();

    loop {
        accum = x.clone() * accum.clone();
        x = x - T::one();

        if x <= T::one() {
            break accum;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn angle() {
        let d90 = Deg(90.0);
        let rad = Rad(d90.radians());

        assert_eq!(d90.radians(), rad.radians());

        let mut x = Deg(346.68);
        x.add(43.59);
        println!("{}", x.degrees());

        let mut y = Rad(pi::<f64>());
        y.add(2.0 * pi::<f64>());
        println!("{}", y.radians()); // slight inaccuracy (0. ...0001)
    }

    #[test]
    fn lerp() {
        for i in [0.00, 0.25, 0.50, 0.75, 1.00] {
            let x = match i {
                0.00 => 0.0,
                0.25 => 50.0,
                0.50 => 100.0,
                0.75 => 150.0,
                1.00 => 200.0,
                _ => unreachable!(),
            };
            assert_eq!(x, super::lerp(0.0, 200.0, i));
        }
    }

    #[test]
    fn fact() {
        assert_eq!(0, super::fact(0));
        assert_eq!(1, super::fact(1));

        assert_eq!(24, super::fact(4));
        assert_eq!(120, super::fact(5));
        assert_eq!(720, super::fact(6));
    }

    #[test]
    fn constants() {
        let pif32 = pi::<f32>();
        println!("pi f32: {pif32}");

        let pif64 = pi::<f64>();
        println!("pi f64: {pif64}");

        let tauf32 = tau::<f32>();
        println!("2pi f32: {tauf32}");

        let phif32 = phi::<f32>();
        println!("phi f32: {phif32}");
    }
}