model-converter/src/math/bounding_box.rs

214 lines
6.5 KiB
Rust

//! Module containing the bounding box struct.
use math::Number;
macro_rules! make_bounding_box {
($name: ident, $vector: ident, $size: expr) => {
use math::vector::$vector;
/// Represents a bounding box.
#[derive(Clone, Debug, PartialEq)]
pub struct $name<T> {
min: $vector<T>,
max: $vector<T>,
}
impl<T: PartialOrd + Copy + Clone> $name<T> {
/// Creates a bounding box from its min and max coordinates.
pub fn new(min: $vector<T>, max: $vector<T>) -> $name<T> {
$name {
min: min,
max: max,
}
}
/// Enlarges the bounding box so it contains the point passed as parameter.
pub fn add_point(&mut self, point: &$vector<T>) {
for i in 0..$size {
if point[i] < self.min[i] {
self.min[i] = point[i];
}
if point[i] > self.max[i] {
self.max[i] = point[i];
}
}
}
/// Returns the minimum value of the bounding box.
pub fn min(&self) -> $vector<T> {
self.min
}
/// Returns the maximum value of the bounding box.
pub fn max(&self) -> $vector<T> {
self.max
}
/// Returns a mut ref to the min.
pub fn min_mut(&mut self) -> &mut $vector<T> {
&mut self.min
}
/// Returns a mut ref to the max.
pub fn max_mut(&mut self) -> &mut $vector<T> {
&mut self.max
}
}
impl<T: Number> $name<T> {
/// Scales a bounding box from its center.
pub fn scale(&mut self, factor: T) {
let diag = self.max - self.min;
self.min -= diag * factor;
self.max += diag * factor;
}
/// Returns the intersection between two bounding boxes.
pub fn intersection(&self, other: &$name<T>) -> $name<T> {
let mut ret = self.clone();
for i in 0..$size {
if ret.min()[i] < other.min()[i] {
ret.min_mut()[i] = other.min()[i];
}
if ret.max()[i] > other.max()[i] {
ret.max_mut()[i] = other.max()[i];
}
}
ret
}
/// Returns the bounding box of the union of two bounding boxes.
pub fn union(&self, other: &$name<T>) -> $name<T> {
let mut ret = self.clone();
for i in 0..$size {
if ret.min()[i] > other.min()[i] {
ret.min_mut()[i] = other.min()[i];
}
if ret.max()[i] < other.max()[i] {
ret.max_mut()[i] = other.max()[i];
}
}
ret
}
/// Returns true if the point is inside the bounding box.
pub fn contains_point(&self, point: $vector<T>) -> bool {
for i in 0 .. $size {
if self.min()[i] > point[i] || point[i] > self.max()[i] {
return false;
}
}
true
}
}
}
}
make_bounding_box!(BoundingBox2, Vector2, 2);
make_bounding_box!(BoundingBox3, Vector3, 3);
make_bounding_box!(BoundingBox4, Vector4, 4);
macro_rules! impl_center {
($name: ident, $vector: ident, $f: tt) => {
impl $name<$f> {
/// Returns the center of the bounding box.
pub fn center(&self) -> $vector<$f> {
(self.min + self.max) / 2.0
}
}
}
}
impl_center!(BoundingBox2, Vector2, f32);
impl_center!(BoundingBox2, Vector2, f64);
impl_center!(BoundingBox3, Vector3, f32);
impl_center!(BoundingBox3, Vector3, f64);
impl_center!(BoundingBox4, Vector4, f32);
impl_center!(BoundingBox4, Vector4, f64);
#[cfg(test)]
mod tests {
macro_rules! assert_delta {
($x:expr, $y:expr, $d:expr) => {
if !($x - $y < $d || $y - $x < $d) { panic!(); }
}
}
use math::vector::Vector2;
use math::bounding_box::BoundingBox2;
use math::vector::Vector3;
use math::bounding_box::BoundingBox3;
#[test]
fn initialization() {
use std::f64::{MIN, MAX};
let mut bounding_box = BoundingBox2::new(
Vector2::new(MAX, MAX),
Vector2::new(MIN, MIN),
);
let v1 = Vector2::new(10.0, 20.0);
bounding_box.add_point(&v1);
assert_delta!(bounding_box.min().x(), v1.x(), 0.01);
assert_delta!(bounding_box.min().y(), v1.y(), 0.01);
assert_delta!(bounding_box.max().x(), v1.x(), 0.01);
assert_delta!(bounding_box.max().y(), v1.y(), 0.01);
let v2 = Vector2::new(20.0, 10.0);
assert_delta!(bounding_box.min().x(), v1.x(), 0.01);
assert_delta!(bounding_box.min().y(), v2.y(), 0.01);
assert_delta!(bounding_box.max().x(), v1.x(), 0.01);
assert_delta!(bounding_box.max().y(), v2.y(), 0.01);
}
#[test]
fn intersection() {
let b1 = BoundingBox3::new(
Vector3::new(0, 0, 0),
Vector3::new(2, 2, 2),
);
let b2 = BoundingBox3::new(
Vector3::new(1, 1, 1),
Vector3::new(3, 3, 3),
);
let bb1 = b1.intersection(&b2);
assert_eq!(bb1.min().x(), 1);
assert_eq!(bb1.min().y(), 1);
assert_eq!(bb1.min().z(), 1);
assert_eq!(bb1.max().x(), 2);
assert_eq!(bb1.max().y(), 2);
assert_eq!(bb1.max().z(), 2);
let bb2 = b2.intersection(&b1);
assert_eq!(bb1, bb2);
}
#[test]
fn contains_point() {
let b1 = BoundingBox3::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(2.0, 2.0, 2.0),
);
assert_eq!(b1.contains_point(Vector3::new(1.0, 1.0, 1.0)), true);
assert_eq!(b1.contains_point(Vector3::new(1.5, 0.5, 1.0)), true);
assert_eq!(b1.contains_point(Vector3::new(1.5, -0.5, 1.0)), false);
assert_eq!(b1.contains_point(Vector3::new(1.5, 0.5, -1.0)), false);
assert_eq!(b1.contains_point(Vector3::new(-0.5, 0.5, 1.0)), false);
assert_eq!(b1.contains_point(Vector3::new(2.5, 0.5, 1.0)), false);
assert_eq!(b1.contains_point(Vector3::new(0.5, 2.5, 1.0)), false);
}
}