bounding-space 0.2.0

N-dimensional bounding space.
Documentation
pub extern crate nalgebra;

use nalgebra::{SVector, RealField, Point};

pub type BoundingSpace1<T> = BoundingSpaceN<T, 1>;
pub type BoundingSpace2<T> = BoundingSpaceN<T, 2>;
pub type BoundingSpace3<T> = BoundingSpaceN<T, 3>;

pub type BoundingRange<T> = BoundingSpace1<T>;
pub type BoundingSquare<T> = BoundingSpace2<T>;
pub type BoundingBox<T> = BoundingSpace3<T>;

#[derive(Debug, Clone, Copy)]
pub struct BoundingSpaceN<T: RealField, const D: usize> {
    pub lower: Point<T, D>,
    pub upper: Point<T, D>,
}

impl<T: RealField, const D: usize> BoundingSpaceN<T, D> {
    pub fn new(lower: Point<T, D>, upper: Point<T, D>) -> Self {
        Self { lower, upper }
    }

    pub fn from_point(point: Point<T, D>) -> Self {
        Self::new(point.to_owned(), point)
    }

    pub fn from_value(value: T) -> Self {
        let coords =  SVector::<T, D>::repeat(value);
        Self {
            lower: Point { coords: coords.to_owned() },
            upper: Point { coords }
        }
    }

    pub fn from_values(lower: T, upper: T) -> Self {
        Self {
            lower: Point { coords: SVector::repeat(lower) },
            upper: Point { coords: SVector::repeat(upper) },
        }
    }

    pub fn diagonal(&self) -> SVector<T, D> {
        &self.upper - &self.lower
    }

    pub fn contains(&self, point: &Point<T, D>) -> bool {
        for (l, p) in self.lower.coords.iter().zip(&point.coords) {
            if l > p {
                return false;
            }
        }

        for (u, p) in self.upper.coords.iter().zip(&point.coords) {
            if u < p {
                return false;
            }
        }

        true
    }

    pub fn expand_lower(&mut self, point: &Point<T, D>) {
        self.lower.coords.zip_apply(&point.coords, |l, p| {
            *l = p.min(l.to_owned());
        });
    }

    pub fn expand_upper(&mut self, point: &Point<T, D>) {
        self.upper.coords.zip_apply(&point.coords, |u, p| {
            *u = p.max(u.to_owned());
        });
    }

    pub fn expand(&mut self, point: &Point<T, D>) {
        self.expand_lower(point);
        self.expand_upper(point);
    }
}

impl<T: RealField, const D: usize> Default for BoundingSpaceN<T, D>
{
    fn default() -> Self {
        Self {
            lower: Point::origin(),
            upper: Point::origin(),
        }
    }
}

#[cfg(test)]
mod tests {
    use approx::assert_relative_eq;
    use nalgebra::Point1;

    use super::*;

    #[test]
    fn default_initialized_with_zeros() {
        let bs = BoundingSpaceN::<f64, 1>::default();

        for v in bs.lower.iter().chain(bs.upper.iter()) {
            assert_relative_eq!(*v, 0.0);
        }
    }

    #[test]
    fn first_expand_nan() {
        let mut bound = BoundingSpaceN::<f64, 1>::from_value(f64::NAN);
        let point = Point1::new(0.0);

        bound.expand(&point);

        assert_relative_eq!(bound.lower.x, point.x);
        assert_relative_eq!(bound.upper.x, point.x);
    }

    #[test]
    fn expand_1d() {
        let value = 0.0_f64;
        let mut bound = BoundingSpaceN::<f64, 1>::from_value(value);

        let p1 = Point1::new(1.0);
        bound.expand(&p1);

        assert_relative_eq!(bound.lower.x, value);
        assert_relative_eq!(bound.upper.x, p1.x);

        let p2 = Point1::new(-1.0);
        bound.expand(&p2);

        assert_relative_eq!(bound.lower.x, p2.x);
        assert_relative_eq!(bound.upper.x, p1.x);
    }
}