bb-geometry 0.3.0

A small crate containing some data structures and methods for 4d Euclidean geometry.
Documentation
use crate::ALMOST_ZERO;
use rand::Rng;

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

pub const X: Vector3D = Vector3D {
    x: 1.0,
    y: 0.0,
    z: 0.0,
};

pub const Y: Vector3D = Vector3D {
    x: 0.0,
    y: 1.0,
    z: 0.0,
};

pub const Z: Vector3D = Vector3D {
    x: 0.0,
    y: 0.0,
    z: 1.0,
};

impl Vector3D {
    /// This creates a new 3d vector with the four given entries.
    pub fn new(x: f64, y: f64, z: f64) -> Vector3D {
        Vector3D { x, y, z }
    }


    /// This gives back the l1 length of a 3d vector, i.e. the maximum of absolute values of its components.
    ///
    /// # Example:
    /// ```
    /// use bb_geometry::vector3d::Vector3D;
    /// let length = Vector3D::new(-5.0, -2.2, 0.0).length_l1();
    /// assert_eq!(length, 5.0);
    /// ```
    pub fn length_l1(&self) -> f64 {
        vec![self.x, self.y, self.z]
            .iter()
            .map(|i| i.abs())
            .reduce(f64::max)
            .unwrap()
    }

    /// This checks whether a 3d vector is close to the zero vector, up to `crate::ALMOST_ZERO`
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::ALMOST_ZERO;
    ///use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::new(ALMOST_ZERO, ALMOST_ZERO, ALMOST_ZERO);
    /// let b = Vector3D::new(0.99 * ALMOST_ZERO, 0.99 * ALMOST_ZERO, 0.99 * ALMOST_ZERO);
    /// assert_eq!(a.is_close_to_zero(), false);
    /// assert_eq!(b.is_close_to_zero(), true);
    /// ```
    pub fn is_close_to_zero(self: &Vector3D) -> bool {
        self.length_l1() < ALMOST_ZERO
    }

    /// This adds two 3d vectors
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::new(1.0, 2.0,  4.0);
    /// let b = Vector3D::new(-1.0, 4.0,  -3.1);
    /// let c = Vector3D::new(0.0, 6.0,  0.9);
    /// assert!(a.plus(&b).almost_equals(&c));
    /// ```
    pub fn plus(self: &Vector3D, a: &Vector3D) -> Vector3D {
        Vector3D {
            x: self.x + a.x,
            y: self.y + a.y,
            z: self.z + a.z,
        }
    }

    /// This subtracts two 3d vectors
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::new(1.0, 2.0,  4.0);
    /// let b = Vector3D::new(-1.0, 4.0, -3.1);
    /// let c = Vector3D::new(2.0, -2.0,  7.1);
    /// assert!(a.minus(&b).almost_equals(&c));
    /// ```
    pub fn minus(self: &Vector3D, a: &Vector3D) -> Vector3D {
        Vector3D {
            x: self.x - a.x,
            y: self.y - a.y,
            z: self.z - a.z,
        }
    }

    /// This compares two 3d vectors up to `crate::ALMOST_ZERO`.
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::new(1.0, 2.0, 3.0);
    /// let b = Vector3D::new(1.00001, 2.0, 3.0);
    /// let c = Vector3D::new(1.0000000001, 2.0, 3.0);
    /// assert_eq!(a.almost_equals(&b), false);
    /// assert_eq!(a.almost_equals(&c), true);
    /// ```
    pub fn almost_equals(self: &Vector3D, a: &Vector3D) -> bool {
        self.minus(a).is_close_to_zero()
    }

    /// This replaces a 3d vector with its negative
    ///
    /// # Examples
    ///
    /// ```
    /// use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::new(1.0, 2.0, 3.0);
    /// let b = Vector3D::new(-1.0, -2.0, -3.0);
    /// assert!(a.revert().almost_equals(&b));
    /// ```
    pub fn revert(&self) -> Vector3D {
        Vector3D {
            x: -self.x,
            y: -self.y,
            z: -self.z,
        }
    }

    /// This generates a 3d vector of length n containing randomly generated 4d vectors.
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::vector3d::Vector3D;
    /// let n = 10;
    /// let list = Vector3D::generate_random_vectors(n);
    /// assert_eq!(list.len(), n);
    /// ```
    pub fn generate_random_vectors(n: usize) -> Vec<Vector3D> {
        (0..n).map(|_| Self::random_vector()).collect()
    }


    /// This generates a random 3d vector with entire between -1.0 and 1.0.
    ///
    /// # Example:
    /// ```
    ///use bb_geometry::vector3d::Vector3D;
    /// let a = Vector3D::random_vector();
    /// assert!(a.length_l1() <= 1.0);
    /// ```
    pub fn random_vector() -> Vector3D {
        let mut rng = rand::rng();
        Vector3D::new(
            rng.random_range(-1.0..=1.0),
            rng.random_range(-1.0..=1.0),
            rng.random_range(-1.0..=1.0),
        )
    }

    pub fn from_reference(vector_reference: &Vector3D) -> Vector3D {
        Vector3D::new(vector_reference.x, vector_reference.y, vector_reference.z)
    }

    pub fn components(&self) -> [f64; 3] {
        [self.x, self.y, self.z]
    }
}

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

    #[test]
    fn plus_and_minus_are_opposite() {
        // given
        let a = Vector3D::random_vector();
        let b = Vector3D::random_vector();

        // when
        let c = a.plus(&b).minus(&b);

        // then
        assert!(c.almost_equals(&a));
    }

    #[test]
    fn minus_and_revert() {
        // given
        let a = Vector3D::random_vector();
        let b = Vector3D::random_vector();

        // when
        let c = a.minus(&(b.revert()));

        // then
        assert!(a.plus(&b).almost_equals(&c));
    }

    #[test]
    fn from_reference_delivers_identical_vector() {
        // given
        let a = Vector3D::random_vector();

        // when
        let b = Vector3D::from_reference(&a);

        // then
        assert!(b.almost_equals(&a));
    }
}