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;
|
use crate::util::Interval;
|
||||||
|
|
||||||
|
|
@ -6,74 +6,7 @@ use super::{IntersectP, Ray};
|
||||||
|
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
pub use crate::util::axis_aligned_bounding_box::BoundingBox;
|
||||||
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]),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: RealField> IntersectP<T> for BoundingBox<T> {
|
impl<T: RealField> IntersectP<T> for BoundingBox<T> {
|
||||||
fn intersect(&self, ray: &Ray<T>) -> bool {
|
fn intersect(&self, ray: &Ray<T>) -> bool {
|
||||||
|
|
@ -100,44 +33,7 @@ mod tests {
|
||||||
use quickcheck::TestResult;
|
use quickcheck::TestResult;
|
||||||
use quickcheck_macros::quickcheck;
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
use nalgebra::Vector3;
|
use nalgebra::{Point3, 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
|
fn wrap_value_in_interval(value: f64, interval: Interval<f64>) -> f64 {
|
||||||
let distance_from_start = (value - interval.get_min()).abs();
|
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));
|
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));
|
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 mod axis_aligned_bounding_box;
|
||||||
pub use axis_aligned_bounding_box::BoundingBox;
|
pub use axis_aligned_bounding_box::BoundingBox;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Ray<T: RealField> {
|
pub struct Ray<T: RealField> {
|
||||||
pub origin: Point3<T>,
|
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;
|
mod interval;
|
||||||
|
|
||||||
|
pub mod axis_aligned_bounding_box;
|
||||||
|
|
||||||
pub use interval::Interval;
|
pub use interval::Interval;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue