mathpak 0.4.0

Rust Math Routines, a simple to use math focused library written in rust
Documentation
use std::cmp::Ordering;

use float_cmp::{ApproxEq, F64Margin};

use super::point::Point;

#[derive(Debug, Clone, Copy)]
pub struct Point3 {
    x: f64,
    y: f64,
    z: f64,
}

impl Point3 {
    pub fn new(x: f64, y: f64, z: f64) -> Self {
        Self { x, y, z }
    }

    pub fn x(&self) -> f64 {
        self.x
    }

    pub fn y(&self) -> f64 {
        self.y
    }

    pub fn z(&self) -> f64 {
        self.z
    }
}

impl Point for Point3 {
    /// Determine if `x`, `y`, and `z` are normal or finite numbers
    fn is_valid(&self) -> bool {
        (self.x.is_normal() || self.x.is_finite())
            && (self.y.is_normal() || self.y.is_finite())
            && (self.z.is_normal() || self.z.is_finite())
    }

    /// Add `other` to self
    fn add(&mut self, other: Self) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }

    /// Subtract `other` from self
    fn sub(&mut self, other: Self) {
        self.x -= other.x;
        self.y -= other.y;
        self.z -= other.z;
    }

    /// Calculate the distance between `self` and `other`
    fn distance(&self, other: Self) -> f64 {
        ((other.x - self.x).powi(2) + (other.y - self.y).powi(2) + (other.z - self.z).powi(2))
            .sqrt()
    }

    /// Calculate the distance between `self` and the origin
    fn distance_from_origin(&self) -> f64 {
        self.distance(Point3::new(0.0, 0.0, 0.0))
    }

    /// Scale `self` by `factor`
    fn scale(&mut self, factor: f64) {
        self.x *= factor;
        self.y *= factor;
        self.z *= factor;
    }

    /// Linearly interpolate between `self` and `other` by `t`
    fn lerp(&self, other: Self, t: f64) -> Self {
        Point3::new(
            self.x + (other.x - self.x) * t,
            self.y + (other.y - self.y) * t,
            self.z + (other.z - self.z) * t,
        )
    }

    /// Compare `self` and `other` with a margin of error
    fn is_equal_marginal(&self, other: Self, margin: F64Margin) -> bool {
        self.x.approx_eq(other.x, margin) && self.y.approx_eq(other.y, margin) && self.z.approx_eq(other.z, margin)
    }
}

/// Implement equality for Point3D
/// Two points are equal if their `x`, `y`, and `z` values are equal within the margin of error
impl PartialEq<Self> for Point3 {
    fn eq(&self, other: &Self) -> bool {
        self.is_equal_marginal(*other, F64Margin::default())
    }
}

impl PartialOrd<Self> for Point3 {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        if !self.is_valid() || !other.is_valid() {
            return None;
        }

        Some(if self == other {
            Ordering::Equal
        } else if self.x > other.x && self.y > other.y && self.z > other.z {
            Ordering::Greater
        } else {
            Ordering::Less
        })
    }
}

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

    #[test]
    fn point3d_eq() {
        let point1 = Point3::new(1.8, 2.0, 3.0);
        let point2 = Point3::new(1.7999999999999998, 2.0, 3.0);
        assert_eq!(point1, point2);
    }

    #[test]
    fn point3d_partialcompare() {
        let p1 = Point3::new(1.0, 2.0, 3.0);
        let p2 = Point3::new(3.0, 4.0, 5.0);
        assert_eq!(p1.partial_cmp(&p2), Some(Ordering::Less));
        assert_eq!(p2.partial_cmp(&p1), Some(Ordering::Greater));
    }

    #[test]
    fn point3d_add() {
        let mut p1 = Point3::new(1.0, 2.0, 3.0);
        let p2 = Point3::new(3.0, 4.0, 5.0);
        p1.add(p2);
        assert_eq!(p1, Point3::new(4.0, 6.0, 8.0));
    }

    #[test]
    fn point3d_sub() {
        let mut p1 = Point3::new(1.0, 2.0, 3.0);
        let p2 = Point3::new(3.0, 4.0, 5.0);
        p1.sub(p2);
        assert_eq!(p1, Point3::new(-2.0, -2.0, -2.0));
    }

    #[test]
    fn point3d_distance() {
        let p1 = Point3::new(1.0, 2.0, 3.0);
        let p2 = Point3::new(4.0, 6.0, 8.0);
        assert_eq!(p1.distance(p2), 7.0710678118654755);
    }

    #[test]
    fn point3d_distance_from_origin() {
        let p = Point3::new(3.0, 4.0, 5.0);
        assert_eq!(p.distance_from_origin(), 7.0710678118654755);
    }

    #[test]
    fn point3d_scale() {
        let mut p = Point3::new(1.0, 2.0, 3.0);
        p.scale(2.0);
        assert_eq!(p, Point3::new(2.0, 4.0, 6.0));
    }

    #[test]
    fn point3d_lerp() {
        let p1 = Point3::new(0.0, 1.0, 2.0);
        let p2 = Point3::new(3.0, 4.0, 5.0);
        assert_eq!(p1.lerp(p2, 0.5), Point3::new(1.5, 2.5, 3.5));
    }

    #[test]
    fn point3d_is_equal_marginal() {
        let p1 = Point3::new(0.0, 1.0, 2.0);
        let p2 = Point3::new(3.0, 4.0, 5.0);
        let pl = p1.lerp(p2, 0.6);
        assert!(pl.is_equal_marginal(Point3::new(1.8, 2.8, 3.8), F64Margin::default()));
    }
}