241 lines
7.2 KiB
Rust
241 lines
7.2 KiB
Rust
use crate::math::Vec3;
|
|
use crate::util::Interval;
|
|
|
|
use itertools::izip;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BoundingBox {
|
|
pub bounds: [Interval; 3],
|
|
}
|
|
|
|
impl BoundingBox {
|
|
pub fn from_corners(a: Vec3<f64>, b: Vec3<f64>) -> Self {
|
|
let mut result = BoundingBox {
|
|
bounds: [Interval::infinite(); 3],
|
|
};
|
|
for (bounds_elem, a_elem, b_elem) in
|
|
izip!(result.bounds.iter_mut(), a.coords.iter(), b.coords.iter())
|
|
{
|
|
*bounds_elem = Interval::new(*a_elem, *b_elem);
|
|
}
|
|
result
|
|
}
|
|
|
|
pub fn empty() -> Self {
|
|
BoundingBox {
|
|
bounds: [Interval::empty(), Interval::empty(), Interval::empty()],
|
|
}
|
|
}
|
|
|
|
pub fn from_point(p: Vec3<f64>) -> Self {
|
|
BoundingBox {
|
|
bounds: [
|
|
Interval::degenerate(p.x()),
|
|
Interval::degenerate(p.y()),
|
|
Interval::degenerate(p.z()),
|
|
],
|
|
}
|
|
}
|
|
|
|
pub fn from_points<'a, I>(points: I) -> Self
|
|
where
|
|
I: IntoIterator<Item = &'a Vec3<f64>>,
|
|
{
|
|
points
|
|
.into_iter()
|
|
.fold(BoundingBox::empty(), |acc, p| acc.expand_to_point(p))
|
|
}
|
|
|
|
pub fn expand_to_point(&self, p: &Vec3<f64>) -> Self {
|
|
BoundingBox {
|
|
bounds: [
|
|
self.bounds[0].expand_to_value(p.x()),
|
|
self.bounds[1].expand_to_value(p.y()),
|
|
self.bounds[2].expand_to_value(p.z()),
|
|
],
|
|
}
|
|
}
|
|
|
|
pub fn contains_point(&self, p: Vec3<f64>) -> bool {
|
|
self.bounds
|
|
.iter()
|
|
.zip(p.coords.iter())
|
|
.all(|(interval, &value)| interval.contains_value(value))
|
|
}
|
|
|
|
pub fn union(&self, other: &BoundingBox) -> BoundingBox {
|
|
BoundingBox {
|
|
bounds: [
|
|
self.bounds[0].union(other.bounds[0]),
|
|
self.bounds[1].union(other.bounds[1]),
|
|
self.bounds[2].union(other.bounds[2]),
|
|
],
|
|
}
|
|
}
|
|
|
|
pub fn largest_dimension(&self) -> usize {
|
|
let (dimension, _) = self
|
|
.bounds
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, elem)| {
|
|
(
|
|
index,
|
|
if elem.is_degenerate() {
|
|
-1.0
|
|
} else {
|
|
elem.get_max() - elem.get_min()
|
|
},
|
|
)
|
|
})
|
|
.fold((0, 0.0), |(acc, acc_size), (elem, elem_size)| {
|
|
if elem_size > acc_size {
|
|
(elem, elem_size)
|
|
} else {
|
|
(acc, acc_size)
|
|
}
|
|
});
|
|
dimension
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use quickcheck_macros::quickcheck;
|
|
|
|
#[test]
|
|
fn from_corners_with_same_point_yields_degenerate_intervals() {
|
|
let test_point = Vec3::new(0f64, 1f64, 2f64);
|
|
let target = BoundingBox::from_corners(test_point, test_point);
|
|
assert!(target.bounds.iter().all(|e| e.is_degenerate()));
|
|
}
|
|
|
|
#[test]
|
|
fn from_corners_yields_same_result_with_any_oposite_corners() {
|
|
let corner_000 = Vec3::new(0.0, 0.0, 0.0);
|
|
let corner_001 = Vec3::new(0.0, 0.0, 1.0);
|
|
let corner_010 = Vec3::new(0.0, 1.0, 0.0);
|
|
let corner_011 = Vec3::new(0.0, 1.0, 1.0);
|
|
let corner_100 = Vec3::new(1.0, 0.0, 0.0);
|
|
let corner_101 = Vec3::new(1.0, 0.0, 1.0);
|
|
let corner_110 = Vec3::new(1.0, 1.0, 0.0);
|
|
let corner_111 = Vec3::new(1.0, 1.0, 1.0);
|
|
|
|
let test_inputs: Vec<(Vec3<f64>, Vec3<f64>)> = vec![
|
|
(corner_000, corner_111),
|
|
(corner_001, corner_110),
|
|
(corner_010, corner_101),
|
|
(corner_011, corner_100),
|
|
(corner_100, corner_011),
|
|
(corner_101, corner_010),
|
|
(corner_110, corner_001),
|
|
(corner_111, corner_000),
|
|
];
|
|
for (a, b) in test_inputs {
|
|
let target = BoundingBox::from_corners(a, b);
|
|
assert!(target
|
|
.bounds
|
|
.iter()
|
|
.all(|bounds| bounds.get_min() == 0.0 && bounds.get_max() == 1.0));
|
|
}
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn union_with_self_yields_self(a: Vec3<f64>, b: Vec3<f64>) -> bool {
|
|
let target = BoundingBox::from_corners(a, b);
|
|
let result = target.union(&target);
|
|
target
|
|
.bounds
|
|
.iter()
|
|
.zip(result.bounds.iter())
|
|
.all(|(a, b)| a.get_min() == b.get_min() && a.get_max() == b.get_max())
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn union_yields_full_ranges(a: Vec3<f64>, b: Vec3<f64>, c: Vec3<f64>, d: Vec3<f64>) -> bool {
|
|
let target1 = BoundingBox::from_corners(a, b);
|
|
let target2 = BoundingBox::from_corners(c, d);
|
|
let result = target1.union(&target2);
|
|
izip!(
|
|
result.bounds.iter(),
|
|
target1.bounds.iter(),
|
|
target2.bounds.iter()
|
|
)
|
|
.all(|(r, t1, t2)| {
|
|
r.get_min() <= t1.get_min()
|
|
&& r.get_min() <= t2.get_min()
|
|
&& r.get_max() >= t1.get_max()
|
|
&& r.get_max() >= t2.get_max()
|
|
})
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn empty_box_contains_no_points(p: Vec3<f64>) -> bool {
|
|
let target = BoundingBox::empty();
|
|
!target.contains_point(p)
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn from_points_produces_box_that_contains_only_points_bounded_by_inputs_on_all_axes(
|
|
p: Vec3<f64>,
|
|
a: Vec3<f64>,
|
|
b: Vec3<f64>,
|
|
c: Vec3<f64>,
|
|
d: Vec3<f64>,
|
|
e: Vec3<f64>,
|
|
) -> bool {
|
|
let points = vec![a, b, c, d, e];
|
|
let target = BoundingBox::from_points(&points);
|
|
let is_in_bounds = points.iter().any(|elem| elem.x() >= p.x())
|
|
&& points.iter().any(|elem| elem.x() <= p.x())
|
|
&& points.iter().any(|elem| elem.y() >= p.y())
|
|
&& points.iter().any(|elem| elem.y() <= p.y())
|
|
&& points.iter().any(|elem| elem.z() >= p.z())
|
|
&& points.iter().any(|elem| elem.z() <= p.z());
|
|
target.contains_point(p) == is_in_bounds
|
|
}
|
|
|
|
#[quickcheck]
|
|
fn no_dimension_is_larger_than_largest_dimension(
|
|
a: f64,
|
|
b: f64,
|
|
c: f64,
|
|
d: f64,
|
|
e: f64,
|
|
f: f64,
|
|
) -> bool {
|
|
let target = BoundingBox {
|
|
bounds: [
|
|
if a > b {
|
|
Interval::empty()
|
|
} else {
|
|
Interval::new(a, b)
|
|
},
|
|
if c > d {
|
|
Interval::empty()
|
|
} else {
|
|
Interval::new(c, d)
|
|
},
|
|
if e > f {
|
|
Interval::empty()
|
|
} else {
|
|
Interval::new(e, f)
|
|
},
|
|
],
|
|
};
|
|
let largest_dimension = target.largest_dimension();
|
|
let largest_bounds = target.bounds[largest_dimension];
|
|
if largest_bounds.is_empty() {
|
|
target.bounds.iter().all(|elem| elem.is_empty())
|
|
} else {
|
|
let largest_size = largest_bounds.get_max() - largest_bounds.get_min();
|
|
target
|
|
.bounds
|
|
.iter()
|
|
.all(|elem| elem.is_empty() || !(largest_size < elem.get_max() - elem.get_min()))
|
|
}
|
|
}
|
|
}
|