rust_3d/
bounding_box_3d.rs

1/*
2Copyright 2017 Martin Buck
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"),
6to deal in the Software without restriction, including without limitation the
7rights to use, copy, modify, merge, publish, distribute, sublicense,
8and/or sell copies of the Software, and to permit persons to whom the Software
9is furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall
12be included all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21*/
22
23//! BoundingBox3D, an axis aligned bounding box within 3D space
24
25use crate::*;
26
27//------------------------------------------------------------------------------
28
29#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30/// BoundingBox3D, an axis aligned bounding box within 3D space
31pub struct BoundingBox3D {
32    min: Point3D,
33    max: Point3D,
34}
35
36impl BoundingBox3D {
37    /// Creates a new BoundingBox3D with the given min and max positions
38    pub fn new<P1, P2>(min: &P1, max: &P2) -> Result<BoundingBox3D>
39    where
40        P1: Is3D,
41        P2: Is3D,
42    {
43        if min.x() == max.x() || min.y() == max.y() || min.z() == max.z() {
44            Err(ErrorKind::MinMaxEqual)
45        } else if min.x() > max.x() || min.y() > max.y() || min.z() > max.z() {
46            Err(ErrorKind::MinMaxSwapped)
47        } else {
48            Ok(BoundingBox3D {
49                min: Point3D {
50                    x: min.x(),
51                    y: min.y(),
52                    z: min.z(),
53                },
54                max: Point3D {
55                    x: max.x(),
56                    y: max.y(),
57                    z: max.z(),
58                },
59            })
60        }
61    }
62    /// Creates a new BoundingBox3D which contains all the given positions
63    pub fn from_iterator<'a, It3D, P>(source: It3D) -> Result<BoundingBox3D>
64    where
65        It3D: IntoIterator<Item = &'a P>,
66        P: 'a + Is3D + Sized,
67    {
68        let mut count = 0;
69
70        let mut minx: f64 = 0.0;
71        let mut miny: f64 = 0.0;
72        let mut minz: f64 = 0.0;
73        let mut maxx: f64 = 0.0;
74        let mut maxy: f64 = 0.0;
75        let mut maxz: f64 = 0.0;
76
77        for p in source {
78            if count == 0 {
79                minx = p.x();
80                miny = p.y();
81                minz = p.z();
82                maxx = p.x();
83                maxy = p.y();
84                maxz = p.z();
85                count += 1;
86                continue;
87            }
88            if p.x() < minx {
89                minx = p.x();
90            }
91            if p.y() < miny {
92                miny = p.y();
93            }
94            if p.z() < minz {
95                minz = p.z();
96            }
97            if p.x() > maxx {
98                maxx = p.x();
99            }
100            if p.y() > maxy {
101                maxy = p.y();
102            }
103            if p.z() > maxz {
104                maxz = p.z();
105            }
106            count += 1;
107        }
108        if count >= 2 {
109            Self::new(
110                &Point3D {
111                    x: minx,
112                    y: miny,
113                    z: minz,
114                },
115                &Point3D {
116                    x: maxx,
117                    y: maxy,
118                    z: maxz,
119                },
120            )
121        } else {
122            Err(ErrorKind::TooFewPoints)
123        }
124    }
125    /// Creates a new BoundingBox3D which contains all the given positions
126    pub fn from_into_iterator<It3D, P>(source: It3D) -> Result<BoundingBox3D>
127    where
128        It3D: IntoIterator<Item = P>,
129        P: Is3D + Sized,
130    {
131        let mut count = 0;
132
133        let mut minx: f64 = 0.0;
134        let mut miny: f64 = 0.0;
135        let mut minz: f64 = 0.0;
136        let mut maxx: f64 = 0.0;
137        let mut maxy: f64 = 0.0;
138        let mut maxz: f64 = 0.0;
139
140        for p in source {
141            if count == 0 {
142                minx = p.x();
143                miny = p.y();
144                minz = p.z();
145                maxx = p.x();
146                maxy = p.y();
147                maxz = p.z();
148                count += 1;
149                continue;
150            }
151            if p.x() < minx {
152                minx = p.x();
153            }
154            if p.y() < miny {
155                miny = p.y();
156            }
157            if p.z() < minz {
158                minz = p.z();
159            }
160            if p.x() > maxx {
161                maxx = p.x();
162            }
163            if p.y() > maxy {
164                maxy = p.y();
165            }
166            if p.z() > maxz {
167                maxz = p.z();
168            }
169            count += 1;
170        }
171        if count >= 2 {
172            Self::new(
173                &Point3D {
174                    x: minx,
175                    y: miny,
176                    z: minz,
177                },
178                &Point3D {
179                    x: maxx,
180                    y: maxy,
181                    z: maxz,
182                },
183            )
184        } else {
185            Err(ErrorKind::TooFewPoints)
186        }
187    }
188    /// Returns the minimum position of the bounding box
189    pub fn min_p(&self) -> Point3D {
190        self.min.clone()
191    }
192    /// Returns the maximum position of the bounding box
193    pub fn max_p(&self) -> Point3D {
194        self.max.clone()
195    }
196    /// Returns the size the bounding box within the x-dimension
197    pub fn size_x(&self) -> Positive {
198        Positive::new((self.max.x() - self.min.x()).abs()).unwrap() //safe since constrain enforced on construction
199    }
200    /// Returns the size the bounding box within the y-dimension
201    pub fn size_y(&self) -> Positive {
202        Positive::new((self.max.y() - self.min.y()).abs()).unwrap() //safe since constrain enforced on construction
203    }
204    /// Returns the size the bounding box within the z-dimension
205    pub fn size_z(&self) -> Positive {
206        Positive::new((self.max.z() - self.min.z()).abs()).unwrap() //safe since constrain enforced on construction
207    }
208    /// Returns the sizes of the bounding box
209    pub fn sizes(&self) -> [Positive; 3] {
210        [self.size_x(), self.size_y(), self.size_z()]
211    }
212    /// Returns the center of the bounding box
213    pub fn center_bb(&self) -> Point3D {
214        Point3D {
215            x: self.min.x() + (self.max.x() - self.min.x()) / 2.0,
216            y: self.min.y() + (self.max.y() - self.min.y()) / 2.0,
217            z: self.min.z() + (self.max.z() - self.min.z()) / 2.0,
218        }
219    }
220    /// Tests whether this bounding box is within the other
221    pub fn is_inside(&self, other: &BoundingBox3D) -> bool {
222        self.min.x() > other.min.x()
223            && self.min.y() > other.min.y()
224            && self.min.z() > other.min.z()
225            && self.max.x() < other.max.x()
226            && self.max.y() < other.max.y()
227            && self.max.z() < other.max.z()
228    }
229    /// Tests whether this bounding box contains a position
230    pub fn contains<P>(&self, other: &P) -> bool
231    where
232        Self: Sized,
233        P: Is3D,
234    {
235        other.x() > self.min.x()
236            && other.x() < self.max.x()
237            && other.y() > self.min.y()
238            && other.y() < self.max.y()
239            && other.z() > self.min.z()
240            && other.z() < self.max.z()
241    }
242    /// Tests whether this bounding box contains the other
243    pub fn has_inside(&self, other: &BoundingBox3D) -> bool {
244        self.min.x() < other.min.x()
245            && self.min.y() < other.min.y()
246            && self.min.z() < other.min.z()
247            && self.max.x() > other.max.x()
248            && self.max.y() > other.max.y()
249            && self.max.z() > other.max.z()
250    }
251    /// Tests whether this bounding box and the other overlap in any way
252    pub fn collides_with(&self, other: &BoundingBox3D) -> bool {
253        2.0 * (self.center_bb().x - other.center_bb().x).abs()
254            < ((self.size_x() + other.size_x()).get())
255            && 2.0 * (self.center_bb().y - other.center_bb().y).abs()
256                < ((self.size_y() + other.size_y()).get())
257            && 2.0 * (self.center_bb().z - other.center_bb().z).abs()
258                < ((self.size_z() + other.size_z()).get())
259    }
260    /// Tests whether this bounding box crosses a certain x value
261    pub fn crossing_x_value(&self, x: f64) -> bool {
262        self.min.x() < x && self.max.x() > x
263    }
264    /// Tests whether this bounding box crosses a certain y value
265    pub fn crossing_y_value(&self, y: f64) -> bool {
266        self.min.y() < y && self.max.y() > y
267    }
268    /// Tests whether this bounding box crosses a certain z value
269    pub fn crossing_z_value(&self, z: f64) -> bool {
270        self.min.z() < z && self.max.z() > z
271    }
272    /// Returns the corner points of the bounding box
273    pub fn corners(&self) -> [Point3D; 8] {
274        [
275            Point3D::new(self.min.x(), self.min.y(), self.min.z()),
276            Point3D::new(self.min.x(), self.min.y(), self.max.z()),
277            Point3D::new(self.min.x(), self.max.y(), self.min.z()),
278            Point3D::new(self.min.x(), self.max.y(), self.max.z()),
279            Point3D::new(self.max.x(), self.min.y(), self.min.z()),
280            Point3D::new(self.max.x(), self.min.y(), self.max.z()),
281            Point3D::new(self.max.x(), self.max.y(), self.min.z()),
282            Point3D::new(self.max.x(), self.max.y(), self.max.z()),
283        ]
284    }
285    /// Returns the distance to another Is3D
286    pub fn distance<P>(&self, other: &P) -> NonNegative
287    where
288        P: Is3D,
289    {
290        let sqr_dist = self.sqr_distance(other).get();
291        NonNegative::new(sqr_dist.sqrt()).unwrap()
292    }
293    /// Returns the square distance to another Is3D
294    pub fn sqr_distance<P>(&self, other: &P) -> NonNegative
295    where
296        P: Is3D,
297    {
298        let dx = max_f64_3(
299            self.min_p().x() - other.x(),
300            0.0,
301            other.x() - self.max_p().x(),
302        );
303        let dy = max_f64_3(
304            self.min_p().y() - other.y(),
305            0.0,
306            other.y() - self.max_p().y(),
307        );
308        let dz = max_f64_3(
309            self.min_p().z() - other.z(),
310            0.0,
311            other.z() - self.max_p().z(),
312        );
313        NonNegative::new(dx * dx + dy * dy + dz * dz).unwrap()
314    }
315}
316
317//------------------------------------------------------------------------------
318
319impl Default for BoundingBox3D {
320    fn default() -> Self {
321        BoundingBox3D {
322            min: Point3D {
323                x: -0.5,
324                y: -0.5,
325                z: -0.5,
326            },
327            max: Point3D {
328                x: 0.5,
329                y: 0.5,
330                z: 0.5,
331            },
332        }
333    }
334}
335
336impl IsND for BoundingBox3D {
337    fn n_dimensions() -> usize {
338        3
339    }
340
341    fn position_nd(&self, dimension: usize) -> Result<f64> {
342        self.center_bb().position_nd(dimension)
343    }
344}
345
346impl Is3D for BoundingBox3D {
347    #[inline(always)]
348    fn x(&self) -> f64 {
349        self.center_bb().x()
350    }
351
352    #[inline(always)]
353    fn y(&self) -> f64 {
354        self.center_bb().y()
355    }
356
357    #[inline(always)]
358    fn z(&self) -> f64 {
359        self.center_bb().z()
360    }
361}
362
363impl IsMovable3D for BoundingBox3D {
364    fn move_by(&mut self, x: f64, y: f64, z: f64) {
365        self.min.move_by(x, y, z);
366        self.max.move_by(x, y, z);
367    }
368}
369
370impl HasBoundingBox3D for BoundingBox3D {
371    fn bounding_box(&self) -> BoundingBox3D {
372        BoundingBox3D::new(&self.min, &self.max).unwrap() // safe
373    }
374}
375
376impl HasBoundingBox3DMaybe for BoundingBox3D {
377    fn bounding_box_maybe(&self) -> Result<BoundingBox3D> {
378        Ok(self.bounding_box())
379    }
380}
381
382impl HasDistanceTo<BoundingBox3D> for BoundingBox3D {
383    fn sqr_distance(&self, other: &BoundingBox3D) -> NonNegative {
384        let mut dx = 0.0;
385        let mut dy = 0.0;
386        let mut dz = 0.0;
387
388        if other.max_p().x() < self.min_p().x() {
389            dx = other.max_p().x() - self.min_p().x();
390        } else if other.min_p().x() > self.max_p().x() {
391            dx = other.min_p().x() - self.max_p().x();
392        }
393
394        if other.max_p().y() < self.min_p().y() {
395            dy = other.max_p().y() - self.min_p().y();
396        } else if other.min_p().y() > self.max_p().y() {
397            dy = other.min_p().y() - self.max_p().y();
398        }
399
400        if other.max_p().z() < self.min_p().z() {
401            dz = other.max_p().z() - self.min_p().z();
402        } else if other.min_p().z() > self.max_p().z() {
403            dz = other.min_p().z() - self.max_p().z();
404        }
405
406        NonNegative::new(dx * dx + dy * dy + dz * dz).unwrap()
407    }
408}
409
410impl IsScalable for BoundingBox3D {
411    fn scale(&mut self, factor: Positive) {
412        let c = self.center_bb();
413        let min_x = c.x - (0.5 * factor.get() * self.size_x().get());
414        let max_x = c.x + (0.5 * factor.get() * self.size_x().get());
415        let min_y = c.y - (0.5 * factor.get() * self.size_y().get());
416        let max_y = c.y + (0.5 * factor.get() * self.size_y().get());
417        let min_z = c.z - (0.5 * factor.get() * self.size_z().get());
418        let max_z = c.z + (0.5 * factor.get() * self.size_z().get());
419
420        self.min.set_xyz(min_x, min_y, min_z);
421        self.max.set_xyz(max_x, max_y, max_z);
422    }
423}
424
425impl IsMergeable for BoundingBox3D {
426    fn consume(&mut self, other: Self) {
427        let (mut min_x, mut min_y, mut min_z) = (self.min.x(), self.min.y(), self.min.z());
428        let (mut max_x, mut max_y, mut max_z) = (self.max.x(), self.max.y(), self.max.z());
429
430        if other.min.x() < min_x {
431            min_x = other.min.x()
432        }
433        if other.min.y() < min_y {
434            min_y = other.min.y()
435        }
436        if other.min.z() < min_z {
437            min_z = other.min.z()
438        }
439
440        if other.max.x() > max_x {
441            max_x = other.max.x()
442        }
443        if other.max.y() > max_y {
444            max_y = other.max.y()
445        }
446        if other.max.z() > max_z {
447            max_z = other.max.z()
448        }
449
450        self.min.set_xyz(min_x, min_y, min_z);
451        self.max.set_xyz(max_x, max_y, max_z);
452    }
453
454    fn combine(&self, other: &Self) -> Self {
455        let (mut min_x, mut min_y, mut min_z) = (self.min.x(), self.min.y(), self.min.z());
456        let (mut max_x, mut max_y, mut max_z) = (self.max.x(), self.max.y(), self.max.z());
457
458        if other.min.x() < min_x {
459            min_x = other.min.x()
460        }
461        if other.min.y() < min_y {
462            min_y = other.min.y()
463        }
464        if other.min.z() < min_z {
465            min_z = other.min.z()
466        }
467
468        if other.max.x() > max_x {
469            max_x = other.max.x()
470        }
471        if other.max.y() > max_y {
472            max_y = other.max.y()
473        }
474        if other.max.z() > max_z {
475            max_z = other.max.z()
476        }
477
478        let min = Point3D::new(min_x, min_y, min_z);
479        let max = Point3D::new(max_x, max_y, max_z);
480
481        BoundingBox3D { min, max }
482    }
483}
484
485impl IsSATObject for BoundingBox3D {
486    fn for_each_point<F>(&self, f: &mut F)
487    where
488        F: FnMut(&Point3D),
489    {
490        let (minx, miny, minz) = (self.min_p().x(), self.min_p().y(), self.min_p().z());
491        let (maxx, maxy, maxz) = (self.max_p().x(), self.max_p().y(), self.max_p().z());
492
493        f(&Point3D::new(minx, miny, minz));
494        f(&Point3D::new(minx, miny, maxz));
495        f(&Point3D::new(minx, maxy, minz));
496        f(&Point3D::new(minx, maxy, maxz));
497        f(&Point3D::new(maxx, miny, minz));
498        f(&Point3D::new(maxx, miny, maxz));
499        f(&Point3D::new(maxx, maxy, minz));
500        f(&Point3D::new(maxx, maxy, maxz));
501    }
502
503    fn for_each_axis<F>(&self, f: &mut F)
504    where
505        F: FnMut(&Norm3D),
506    {
507        f(&Norm3D::norm_x());
508        f(&Norm3D::norm_y());
509        f(&Norm3D::norm_z());
510    }
511}