Move axis_aligned_bounding_box to util module
This commit is contained in:
parent
65b5e3c45d
commit
c3f3fffc0e
|
|
@ -1,4 +1,4 @@
|
|||
use nalgebra::{Point3, RealField};
|
||||
use nalgebra::RealField;
|
||||
|
||||
use crate::util::Interval;
|
||||
|
||||
|
|
@ -6,74 +6,7 @@ use super::{IntersectP, Ray};
|
|||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BoundingBox<T: RealField> {
|
||||
bounds: [Interval<T>; 3],
|
||||
}
|
||||
|
||||
impl<T: RealField> BoundingBox<T> {
|
||||
pub fn from_corners(a: Point3<T>, b: Point3<T>) -> Self {
|
||||
let mut result = BoundingBox {
|
||||
bounds: [Interval::infinite(); 3],
|
||||
};
|
||||
for (bounds_elem, a_elem, b_elem) in izip!(result.bounds.iter_mut(), a.iter(), b.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: Point3<T>) -> 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 Point3<T>>,
|
||||
{
|
||||
points
|
||||
.into_iter()
|
||||
.fold(BoundingBox::empty(), |acc, p| acc.expand_to_point(p))
|
||||
}
|
||||
|
||||
pub fn expand_to_point(&self, p: &Point3<T>) -> 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: Point3<T>) -> bool {
|
||||
self.bounds
|
||||
.iter()
|
||||
.zip(p.iter())
|
||||
.all(|(interval, &value)| interval.contains_value(value))
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &BoundingBox<T>) -> BoundingBox<T> {
|
||||
BoundingBox {
|
||||
bounds: [
|
||||
self.bounds[0].union(other.bounds[0]),
|
||||
self.bounds[1].union(other.bounds[1]),
|
||||
self.bounds[2].union(other.bounds[2]),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use crate::util::axis_aligned_bounding_box::BoundingBox;
|
||||
|
||||
impl<T: RealField> IntersectP<T> for BoundingBox<T> {
|
||||
fn intersect(&self, ray: &Ray<T>) -> bool {
|
||||
|
|
@ -100,44 +33,7 @@ mod tests {
|
|||
use quickcheck::TestResult;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
use nalgebra::Vector3;
|
||||
|
||||
#[test]
|
||||
fn from_corners_with_same_point_yields_degenerate_intervals() {
|
||||
let test_point = Point3::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 = Point3::new(0.0, 0.0, 0.0);
|
||||
let corner_001 = Point3::new(0.0, 0.0, 1.0);
|
||||
let corner_010 = Point3::new(0.0, 1.0, 0.0);
|
||||
let corner_011 = Point3::new(0.0, 1.0, 1.0);
|
||||
let corner_100 = Point3::new(1.0, 0.0, 0.0);
|
||||
let corner_101 = Point3::new(1.0, 0.0, 1.0);
|
||||
let corner_110 = Point3::new(1.0, 1.0, 0.0);
|
||||
let corner_111 = Point3::new(1.0, 1.0, 1.0);
|
||||
|
||||
let test_inputs: Vec<(Point3<f64>, Point3<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));
|
||||
}
|
||||
}
|
||||
use nalgebra::{Point3, Vector3};
|
||||
|
||||
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
|
||||
let distance_from_start = (value - interval.get_min()).abs();
|
||||
|
|
@ -226,64 +122,4 @@ mod tests {
|
|||
let z_ray = Ray::new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0));
|
||||
assert!(!target.intersect(&z_ray));
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn union_with_self_yields_self(a: Point3<f64>, b: Point3<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: Point3<f64>,
|
||||
b: Point3<f64>,
|
||||
c: Point3<f64>,
|
||||
d: Point3<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: Point3<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: Point3<f64>,
|
||||
a: Point3<f64>,
|
||||
b: Point3<f64>,
|
||||
c: Point3<f64>,
|
||||
d: Point3<f64>,
|
||||
e: Point3<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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ pub use triangle::Triangle;
|
|||
pub mod axis_aligned_bounding_box;
|
||||
pub use axis_aligned_bounding_box::BoundingBox;
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ray<T: RealField> {
|
||||
pub origin: Point3<T>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
use nalgebra::{Point3, RealField};
|
||||
|
||||
use crate::util::Interval;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BoundingBox<T: RealField> {
|
||||
pub bounds: [Interval<T>; 3],
|
||||
}
|
||||
|
||||
impl<T: RealField> BoundingBox<T> {
|
||||
pub fn from_corners(a: Point3<T>, b: Point3<T>) -> Self {
|
||||
let mut result = BoundingBox {
|
||||
bounds: [Interval::infinite(); 3],
|
||||
};
|
||||
for (bounds_elem, a_elem, b_elem) in izip!(result.bounds.iter_mut(), a.iter(), b.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: Point3<T>) -> 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 Point3<T>>,
|
||||
{
|
||||
points
|
||||
.into_iter()
|
||||
.fold(BoundingBox::empty(), |acc, p| acc.expand_to_point(p))
|
||||
}
|
||||
|
||||
pub fn expand_to_point(&self, p: &Point3<T>) -> 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: Point3<T>) -> bool {
|
||||
self.bounds
|
||||
.iter()
|
||||
.zip(p.iter())
|
||||
.all(|(interval, &value)| interval.contains_value(value))
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &BoundingBox<T>) -> BoundingBox<T> {
|
||||
BoundingBox {
|
||||
bounds: [
|
||||
self.bounds[0].union(other.bounds[0]),
|
||||
self.bounds[1].union(other.bounds[1]),
|
||||
self.bounds[2].union(other.bounds[2]),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn from_corners_with_same_point_yields_degenerate_intervals() {
|
||||
let test_point = Point3::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 = Point3::new(0.0, 0.0, 0.0);
|
||||
let corner_001 = Point3::new(0.0, 0.0, 1.0);
|
||||
let corner_010 = Point3::new(0.0, 1.0, 0.0);
|
||||
let corner_011 = Point3::new(0.0, 1.0, 1.0);
|
||||
let corner_100 = Point3::new(1.0, 0.0, 0.0);
|
||||
let corner_101 = Point3::new(1.0, 0.0, 1.0);
|
||||
let corner_110 = Point3::new(1.0, 1.0, 0.0);
|
||||
let corner_111 = Point3::new(1.0, 1.0, 1.0);
|
||||
|
||||
let test_inputs: Vec<(Point3<f64>, Point3<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: Point3<f64>, b: Point3<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: Point3<f64>,
|
||||
b: Point3<f64>,
|
||||
c: Point3<f64>,
|
||||
d: Point3<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: Point3<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: Point3<f64>,
|
||||
a: Point3<f64>,
|
||||
b: Point3<f64>,
|
||||
c: Point3<f64>,
|
||||
d: Point3<f64>,
|
||||
e: Point3<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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
mod interval;
|
||||
|
||||
pub mod axis_aligned_bounding_box;
|
||||
|
||||
pub use interval::Interval;
|
||||
|
|
|
|||
Loading…
Reference in New Issue