diff --git a/beevee/src/aabb/bounding_box.rs b/beevee/src/aabb/bounding_box.rs index 6c40f9d..dfe7a7d 100644 --- a/beevee/src/aabb/bounding_box.rs +++ b/beevee/src/aabb/bounding_box.rs @@ -1,5 +1,412 @@ //! An Axis-Alighned Bounding Box. -/// An axis-aligned bounding box. +use crate::{Axis, Point, Vector}; +use std::fmt::{Display, Formatter, Result}; + +/// An Axis-Aligned Bounding Box. #[derive(Copy, Clone, Debug, PartialEq)] -pub struct AABB {} +pub struct AABB { + /// The corner with the lowest (x, y, z) coordinates. + pub low: Point, + /// The corner with the highest (x, y, z) coordinates. + pub high: Point, +} + +impl AABB { + /// Create a new empty [`AABB`] + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// + /// ``` + /// use beevee::Point; + /// use beevee::aabb::AABB; + /// + /// let aabb = AABB::empty(); + /// + /// // Here for teh origin, but also true for any other point + /// let point = Point::origin(); + /// + /// assert!(!aabb.contains(&point)); + /// ``` + #[must_use] + pub fn empty() -> Self { + let lowest = std::f32::NEG_INFINITY; + let highest = std::f32::INFINITY; + AABB { + low: Point::new(highest, highest, highest), + high: Point::new(lowest, lowest, lowest), + } + } + + /// Create a new empty [`AABB`] + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb, AABB::with_bounds(low, high)); + /// ``` + #[must_use] + pub fn with_bounds(low: Point, high: Point) -> Self { + debug_assert!(low.x <= high.x); + debug_assert!(low.y <= high.y); + debug_assert!(low.z <= high.z); + AABB { low, high } + } + + /// Return a new bounding box containing both `self` and the new [`Point`] + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let aabb = AABB::empty(); + /// let new_aabb = aabb.grow(&Point::origin()); + /// + /// assert_eq!(new_aabb, AABB::with_bounds(Point::origin(), Point::origin())); + /// ``` + #[must_use] + pub fn grow(&self, point: &Point) -> Self { + let mut ans = *self; + ans.grow_mut(point); + ans + } + + /// Grow the bounding box to accomodate a new [`Point`]. + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let mut aabb = AABB::empty(); + /// aabb.grow_mut(&Point::origin()); + /// + /// assert_eq!(aabb, AABB::with_bounds(Point::origin(), Point::origin())); + /// ``` + pub fn grow_mut(&mut self, point: &Point) -> &mut Self { + // Update lowest bound + self.low.x = self.low.x.min(point.x); + self.low.y = self.low.y.min(point.y); + self.low.z = self.low.z.min(point.z); + // Update higher bound + self.high.x = self.high.x.max(point.x); + self.high.y = self.high.y.max(point.y); + self.high.z = self.high.z.max(point.z); + // Return self for method chaining + self + } + + /// Return true if the bounding box contains the [`Point`], false otherwise + /// + /// [`Point`]: ../type.Point.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // It contains the whole box from low to high + /// assert!(aabb.contains(&low)); + /// assert!(aabb.contains(&high)); + /// assert!(aabb.contains(&Point::new(0.5, 0.5, 0.5))); + /// + /// // And doesn't contain anything else + /// assert!(!aabb.contains(&Point::new(-1., -1., -1.))); + /// assert!(!aabb.contains(&Point::new(1.1, 0., 0.))); + /// assert!(!aabb.contains(&Point::new(2., -2., 0.))); + /// ``` + pub fn contains(&self, point: &Point) -> bool { + (self.low.x..=self.high.x).contains(&point.x) + && (self.low.y..=self.high.y).contains(&point.y) + && (self.low.z..=self.high.z).contains(&point.z) + } + + /// Return a new `AABB` which encloses `self` and the other [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::empty(); + /// let other = AABB::with_bounds(low, high); + /// + /// // Grow the AABB to enclose the other one. + /// let union = aabb.union(&other); + /// + /// // The result is now the union of an empty bounding box and the other one. + /// assert_eq!(union, other); + /// ``` + pub fn union(&self, other: &Self) -> Self { + // Clone the first bounding box. + let mut ans = *self; + // Update the new bounding box. + ans.union_mut(other); + // Return the new bounding box. + ans + } + + /// Grow `self` to enclose the other [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let mut aabb = AABB::empty(); + /// let other = AABB::with_bounds(low, high); + /// + /// // Grow the AABB to enclose the other one. + /// aabb.union_mut(&other); + /// + /// // The bounding box has grown to become equal to other one. + /// assert_eq!(aabb, other); + /// ``` + pub fn union_mut(&mut self, other: &Self) -> &mut Self { + self.grow_mut(&other.low); + self.grow_mut(&other.high); + self + } + + /// Return a vector correspondin to the diagonal from `low` to `high` for the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.diagonal(), high - low); + /// ``` + pub fn diagonal(&self) -> Vector { + self.high - self.low + } + + /// Return the center of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.centroid(), low + (high - low) / 2.); + /// ``` + pub fn centroid(&self) -> Point { + self.low + self.diagonal() / 2. + } + + /// Return true if the [`AABB`] is empty, false otherwise. + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let not_empty = AABB::with_bounds(low, high); + /// let empty = AABB::empty(); + /// + /// assert!(!not_empty.is_empty()); + /// assert!(empty.is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.low.x > self.high.x || self.low.y > self.high.y || self.low.z > self.high.z + } + + /// Return the total surface area of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.surface() - 6.).abs() < std::f32::EPSILON); + /// ``` + pub fn surface(&self) -> f32 { + let diagonal = self.diagonal(); + 2. * (diagonal.x * diagonal.y + diagonal.x * diagonal.z + diagonal.y * diagonal.z) + } + + /// Return the total volume of the [`AABB`]. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.volume() - 1.).abs() < std::f32::EPSILON); + /// ``` + pub fn volume(&self) -> f32 { + let diagonal = self.diagonal(); + diagonal.x * diagonal.y * diagonal.z + } + + /// Return the axis along which the [`AABB`] is the largest. + /// + /// [`AABB`]: struct.AABB.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// use beevee::Axis; + /// + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(3., 2., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert_eq!(aabb.largest_axis(), Axis::X); + /// ``` + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// use beevee::Axis; + /// + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // Prefers the X axis in case of a three-way tie, then the Y axis in a tie with Z + /// assert_eq!(aabb.largest_axis(), Axis::X); + /// ``` + pub fn largest_axis(&self) -> Axis { + let diagonal = self.diagonal(); + if diagonal.x >= diagonal.y && diagonal.x >= diagonal.z { + Axis::X + } else if diagonal.y >= diagonal.z { + Axis::Y + } else { + Axis::Z + } + } + + /// Return the shortest distance from an [`AABB`] to a [`Point`]. + /// + /// [`AABB`]: struct.AABB.html + /// [`AABB`]: ../type.Point.html + /// + /// # Examples + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// assert!((aabb.distance_to_point(Point::new(-1., 0., 0.)) - 1.).abs() < std::f32::EPSILON); + /// ``` + /// + /// ``` + /// # use beevee::Point; + /// # use beevee::aabb::AABB; + /// # + /// let low = Point::new(0., 0., 0.); + /// let high = Point::new(1., 1., 1.); + /// let aabb = AABB::with_bounds(low, high); + /// + /// // Returns 0. when the point is contained by the AABB + /// assert!(aabb.distance_to_point(Point::new(0.5, 0.5, 0.5)).abs() < std::f32::EPSILON); + /// ``` + pub fn distance_to_point(&self, point: Point) -> f32 { + let dx = (self.low.x - point.x).max(0.).max(point.x - self.high.x); + let dy = (self.low.y - point.y).max(0.).max(point.y - self.high.y); + let dz = (self.low.z - point.z).max(0.).max(point.z - self.high.z); + f32::sqrt(dx * dx + dy * dy + dz * dz) + } +} + +/// Display implementation for [`AABB`]. +/// +/// [`AABB`]: struct.AABB.html +/// +/// # Examples +/// ``` +/// # use beevee::Point; +/// # use beevee::aabb::AABB; +/// let low = Point::new(0., 0., 0.); +/// let high = Point::new(1., 1., 1.); +/// let aabb = AABB::with_bounds(low, high); +/// +/// assert_eq!(format!("{}", aabb), "low: {0, 0, 0}, high: {1, 1, 1}"); +/// ``` +impl Display for AABB { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "low: {}, high: {}", self.low, self.high) + } +} + +/// Return an empty [`AABB`]. +/// +/// [`AABB`]: struct.AABB.html +/// +/// # Examples +/// +/// ``` +/// # use beevee::aabb::AABB; +/// let default = ::default(); +/// let empty = AABB::empty(); +/// +/// assert_eq!(default, empty); +/// ``` +impl Default for AABB { + fn default() -> Self { + AABB::empty() + } +}