beevee: aabb: add AABB implementation

This commit is contained in:
Bruno BELANYI 2020-03-24 01:39:46 +01:00
parent ca28281b67
commit 58ee42d21d

View file

@ -1,5 +1,412 @@
//! An Axis-Alighned Bounding Box. //! 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)] #[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 = <AABB as Default>::default();
/// let empty = AABB::empty();
///
/// assert_eq!(default, empty);
/// ```
impl Default for AABB {
fn default() -> Self {
AABB::empty()
}
}