bbox/
lib.rs

1//! bbox is crate for managing axis aligned 3d Bounding Boxes.
2//! Bounding Boxes can be created, dilated, transformed and joined with other Bounding Boxes using
3//! CSG operations.
4//! Finally you can test whether or not a Bounding Box contains some point and what approximate
5//! distance a Point has to the Box.
6//! # Examples
7//!
8//! Intersect two Bounding Boxes:
9//!
10//! ```rust,no_run
11//! use nalgebra as na;
12//! let bbox1 = bbox::BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.),
13//!                                           &na::Point3::new(1., 2., 3.));
14//! let bbox2 = bbox::BoundingBox::<f64>::new(&na::Point3::new(-1., -2., -3.),
15//!                                           &na::Point3::new(3., 2., 1.));
16//! let intersection = bbox1.intersection(&bbox2);
17//! ```
18//! Rotate a Bounding Box:
19//!
20//! ```rust
21//! use nalgebra as na;
22//! let rotation = na::Rotation::from_euler_angles(10., 11., 12.);
23//! let bbox = bbox::BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.),
24//!                                          &na::Point3::new(1., 2., 3.));
25//! let rotated_box = bbox.transform(&rotation.to_homogeneous());
26//! ```
27//! Is a point contained in the Box?
28//!
29//! ```rust
30//! use nalgebra as na;
31//! let bbox = bbox::BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.),
32//!                                          &na::Point3::new(1., 2., 3.));
33//! let result = bbox.contains(&na::Point3::new(1., 1., 1.));
34//! ```
35//! Calculate approximate distance of a point to the Box:
36//!
37//! ```rust
38//! use nalgebra as na;
39//! let bbox = bbox::BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.),
40//!                                          &na::Point3::new(1., 2., 3.));
41//! let distance = bbox.distance(&na::Point3::new(1., 1., 1.));
42//! ```
43//! ## Cargo Features
44//!
45//! * `mint` - Enable interoperation with other math libraries through the
46//!   [`mint`](https://crates.io/crates/mint) interface.
47
48#![warn(missing_docs)]
49
50use approx::{AbsDiffEq, RelativeEq};
51use nalgebra as na;
52use num_traits::Float;
53use std::fmt::Debug;
54
55/// 3D Bounding Box - defined by two diagonally opposing points.
56#[derive(Clone, Debug, PartialEq)]
57pub struct BoundingBox<S: 'static + Debug + Copy + PartialEq> {
58    /// X-Y-Z-Minimum corner of the box.
59    pub min: na::Point3<S>,
60    /// X-Y-Z-Maximum corner of the box.
61    pub max: na::Point3<S>,
62}
63
64fn point_min<S: 'static + Float + Debug>(p: &[na::Point3<S>]) -> na::Point3<S> {
65    p.iter().fold(
66        na::Point3::<S>::new(S::infinity(), S::infinity(), S::infinity()),
67        |mut min, current| {
68            min.x = min.x.min(current.x);
69            min.y = min.y.min(current.y);
70            min.z = min.z.min(current.z);
71            min
72        },
73    )
74}
75fn point_max<S: 'static + Float + Debug>(p: &[na::Point3<S>]) -> na::Point3<S> {
76    p.iter().fold(
77        na::Point3::<S>::new(S::neg_infinity(), S::neg_infinity(), S::neg_infinity()),
78        |mut max, current| {
79            max.x = max.x.max(current.x);
80            max.y = max.y.max(current.y);
81            max.z = max.z.max(current.z);
82            max
83        },
84    )
85}
86
87impl<S: Float + Debug + na::RealField + simba::scalar::RealField> BoundingBox<S> {
88    /// Returns an infinte sized box.
89    pub fn infinity() -> BoundingBox<S> {
90        BoundingBox {
91            min: na::Point3::<S>::new(S::neg_infinity(), S::neg_infinity(), S::neg_infinity()),
92            max: na::Point3::<S>::new(S::infinity(), S::infinity(), S::infinity()),
93        }
94    }
95    /// Returns a negatively infinte sized box.
96    pub fn neg_infinity() -> BoundingBox<S> {
97        BoundingBox {
98            min: na::Point3::<S>::new(S::infinity(), S::infinity(), S::infinity()),
99            max: na::Point3::<S>::new(S::neg_infinity(), S::neg_infinity(), S::neg_infinity()),
100        }
101    }
102    /// Create a new Bounding Box by supplying two points.
103    pub fn new(a: &na::Point3<S>, b: &na::Point3<S>) -> BoundingBox<S> {
104        BoundingBox {
105            min: na::Point3::<S>::new(
106                Float::min(a.x, b.x),
107                Float::min(a.y, b.y),
108                Float::min(a.z, b.z),
109            ),
110            max: na::Point3::<S>::new(
111                Float::max(a.x, b.x),
112                Float::max(a.y, b.y),
113                Float::max(a.z, b.z),
114            ),
115        }
116    }
117    /// Create a CSG Union of two Bounding Boxes.
118    pub fn union(&self, other: &BoundingBox<S>) -> BoundingBox<S> {
119        BoundingBox {
120            min: point_min(&[self.min, other.min]),
121            max: point_max(&[self.max, other.max]),
122        }
123    }
124    /// Create a CSG Intersection of two Bounding Boxes.
125    pub fn intersection(&self, other: &BoundingBox<S>) -> BoundingBox<S> {
126        BoundingBox {
127            min: point_max(&[self.min, other.min]),
128            max: point_min(&[self.max, other.max]),
129        }
130    }
131    /// Transform a Bounding Box - resulting in a enclosing axis aligned Bounding Box.
132    pub fn transform(&self, mat: &na::Matrix4<S>) -> BoundingBox<S> {
133        let a = &self.min;
134        let b = &self.max;
135        let corners = [
136            mat.transform_point(&na::Point3::<S>::new(a.x, a.y, a.z)),
137            mat.transform_point(&na::Point3::<S>::new(a.x, a.y, b.z)),
138            mat.transform_point(&na::Point3::<S>::new(a.x, b.y, a.z)),
139            mat.transform_point(&na::Point3::<S>::new(a.x, b.y, b.z)),
140            mat.transform_point(&na::Point3::<S>::new(b.x, a.y, a.z)),
141            mat.transform_point(&na::Point3::<S>::new(b.x, a.y, b.z)),
142            mat.transform_point(&na::Point3::<S>::new(b.x, b.y, a.z)),
143            mat.transform_point(&na::Point3::<S>::new(b.x, b.y, b.z)),
144        ];
145        BoundingBox {
146            min: point_min(&corners),
147            max: point_max(&corners),
148        }
149    }
150    /// Dilate a Bounding Box by some amount in all directions.
151    pub fn dilate(&mut self, d: S) -> &mut Self {
152        self.min.x -= d;
153        self.min.y -= d;
154        self.min.z -= d;
155        self.max.x += d;
156        self.max.y += d;
157        self.max.z += d;
158        self
159    }
160    /// Add a Point to a Bounding Box, e.g. expand the Bounding Box to contain that point.
161    pub fn insert(&mut self, o: &na::Point3<S>) -> &mut Self {
162        self.min.x = Float::min(self.min.x, o.x);
163        self.min.y = Float::min(self.min.y, o.y);
164        self.min.z = Float::min(self.min.z, o.z);
165        self.max.x = Float::max(self.max.x, o.x);
166        self.max.y = Float::max(self.max.y, o.y);
167        self.max.z = Float::max(self.max.z, o.z);
168        self
169    }
170    /// Return the size of the Box.
171    pub fn dim(&self) -> na::Vector3<S> {
172        self.max - self.min
173    }
174    /// Returns the approximate distance of p to the box. The result is guarateed to be not less
175    /// than the euclidean distance of p to the box.
176    pub fn distance(&self, p: &na::Point3<S>) -> S {
177        // If p is not inside (neg), then it is outside (pos) on only one side.
178        // So so calculating the max of the diffs on both sides should result in the true value,
179        // if positive.
180        let xval = Float::max(p.x - self.max.x, self.min.x - p.x);
181        let yval = Float::max(p.y - self.max.y, self.min.y - p.y);
182        let zval = Float::max(p.z - self.max.z, self.min.z - p.z);
183        Float::max(xval, Float::max(yval, zval))
184    }
185    /// Return true if the Bounding Box contains p.
186    pub fn contains(&self, p: &na::Point3<S>) -> bool {
187        p.x >= self.min.x
188            && p.x <= self.max.x
189            && p.y >= self.min.y
190            && p.y <= self.max.y
191            && p.z >= self.min.z
192            && p.z <= self.max.z
193    }
194}
195
196impl<T: Float> AbsDiffEq for BoundingBox<T>
197where
198    <T as AbsDiffEq>::Epsilon: Copy,
199    T: AbsDiffEq + Debug,
200{
201    type Epsilon = <T as AbsDiffEq>::Epsilon;
202
203    fn default_epsilon() -> Self::Epsilon {
204        <T as AbsDiffEq>::default_epsilon()
205    }
206
207    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
208        na::Point3::abs_diff_eq(&self.min, &other.min, epsilon)
209            && na::Point3::abs_diff_eq(&self.max, &other.max, epsilon)
210    }
211}
212
213impl<T: Float> RelativeEq for BoundingBox<T>
214where
215    <T as AbsDiffEq>::Epsilon: Copy,
216    T: RelativeEq + Debug,
217{
218    fn default_max_relative() -> <T as AbsDiffEq>::Epsilon {
219        <T as RelativeEq>::default_max_relative()
220    }
221
222    fn relative_eq(
223        &self,
224        other: &Self,
225        epsilon: <T as AbsDiffEq>::Epsilon,
226        max_relative: <T as AbsDiffEq>::Epsilon,
227    ) -> bool {
228        na::Point3::relative_eq(&self.min, &other.min, epsilon, max_relative)
229            && na::Point3::relative_eq(&self.max, &other.max, epsilon, max_relative)
230    }
231}
232
233#[cfg(test)]
234mod test {
235    use super::*;
236    use approx::assert_relative_eq;
237
238    #[test]
239    fn box_contains_points_inside() {
240        let bbox =
241            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(1., 2., 3.));
242        assert!(bbox.contains(&na::Point3::new(0., 0., 0.)));
243        assert!(bbox.contains(&na::Point3::new(0., 1., 0.)));
244        assert!(bbox.contains(&na::Point3::new(1., 0., 1.)));
245        assert!(bbox.contains(&na::Point3::new(1., 1., 1.)));
246        assert!(!bbox.contains(&na::Point3::new(2., 2., 2.)));
247        assert!(!bbox.contains(&na::Point3::new(-1., -1., -1.)));
248    }
249
250    #[test]
251    fn transform_with_translation() {
252        let bbox =
253            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(1., 1., 1.));
254        assert_relative_eq!(
255            bbox.transform(&na::Translation3::new(1., 2., 3.).to_homogeneous()),
256            BoundingBox::<f64>::new(&na::Point3::new(1., 2., 3.), &na::Point3::new(2., 3., 4.),)
257        );
258    }
259
260    #[test]
261    fn transform_with_rotation() {
262        let bbox =
263            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(1., 1., 1.));
264        assert_relative_eq!(
265            bbox.transform(
266                &na::Rotation::from_euler_angles(::std::f64::consts::PI / 2., 0., 0.)
267                    .to_homogeneous()
268            ),
269            BoundingBox::<f64>::new(&na::Point3::new(0., -1., 0.), &na::Point3::new(1., 0., 1.),)
270        );
271    }
272
273    #[test]
274    fn union_of_two_boxes() {
275        let bbox1 =
276            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(4., 8., 16.));
277        let bbox2 =
278            BoundingBox::<f64>::new(&na::Point3::new(2., 2., 2.), &na::Point3::new(16., 4., 8.));
279        assert_relative_eq!(
280            bbox1.union(&bbox2),
281            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(16., 8., 16.),)
282        );
283    }
284
285    #[test]
286    fn intersection_of_two_boxes() {
287        let bbox1 =
288            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(4., 8., 16.));
289        let bbox2 =
290            BoundingBox::<f64>::new(&na::Point3::new(2., 2., 2.), &na::Point3::new(16., 4., 8.));
291        assert_relative_eq!(
292            bbox1.intersection(&bbox2),
293            BoundingBox::<f64>::new(&na::Point3::new(2., 2., 2.), &na::Point3::new(4., 4., 8.),)
294        );
295    }
296
297    #[test]
298    fn dilate() {
299        let mut bbox =
300            BoundingBox::<f64>::new(&na::Point3::new(0., 0., 0.), &na::Point3::new(1., 1., 1.));
301        assert_relative_eq!(
302            bbox.dilate(0.1),
303            &mut BoundingBox::<f64>::new(
304                &na::Point3::new(-0.1, -0.1, -0.1),
305                &na::Point3::new(1.1, 1.1, 1.1),
306            )
307        );
308        assert_relative_eq!(
309            bbox.dilate(-0.5),
310            &mut BoundingBox::<f64>::new(
311                &na::Point3::new(0.4, 0.4, 0.4),
312                &na::Point3::new(0.6, 0.6, 0.6),
313            )
314        );
315    }
316
317    #[test]
318    fn box_contains_inserted_points() {
319        let mut bbox = BoundingBox::neg_infinity();
320        let p1 = na::Point3::new(1., 0., 0.);
321        let p2 = na::Point3::new(0., 2., 3.);
322        assert!(!bbox.contains(&p1));
323        bbox.insert(&p1);
324        assert!(bbox.contains(&p1));
325        assert!(!bbox.contains(&p2));
326        bbox.insert(&p2);
327        assert!(bbox.contains(&p2));
328    }
329}