rust_3d/
bounding_box_2d.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//! BoundingBox2D, an axis aligned bounding box within 2D space
24
25use crate::*;
26
27//------------------------------------------------------------------------------
28
29#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30/// BoundingBox2D, an axis aligned bounding box within 2D space
31pub struct BoundingBox2D {
32    min: Point2D,
33    max: Point2D,
34}
35
36impl BoundingBox2D {
37    /// Creates a new BoundingBox2D with the given min and max positions
38    pub fn new<P1, P2>(min: &P1, max: &P2) -> Result<BoundingBox2D>
39    where
40        P1: Is2D,
41        P2: Is2D,
42    {
43        if min.x() == max.x() || min.y() == max.y() {
44            Err(ErrorKind::MinMaxEqual)
45        } else if min.x() > max.x() || min.y() > max.y() {
46            Err(ErrorKind::MinMaxSwapped)
47        } else {
48            Ok(BoundingBox2D {
49                min: Point2D {
50                    x: min.x(),
51                    y: min.y(),
52                },
53                max: Point2D {
54                    x: max.x(),
55                    y: max.y(),
56                },
57            })
58        }
59    }
60    /// Creates a new BoundingBox2D which contains all the given positions
61    pub fn from_iterator<'a, It2D, P>(source: It2D) -> Result<BoundingBox2D>
62    where
63        It2D: IntoIterator<Item = &'a P>,
64        P: 'a + Is2D + Sized,
65    {
66        let mut count = 0;
67
68        let mut minx: f64 = 0.0;
69        let mut miny: f64 = 0.0;
70        let mut maxx: f64 = 0.0;
71        let mut maxy: f64 = 0.0;
72
73        for p in source {
74            if count == 0 {
75                minx = p.x();
76                miny = p.y();
77                maxx = p.x();
78                maxy = p.y();
79                count += 1;
80                continue;
81            }
82            if p.x() < minx {
83                minx = p.x();
84            }
85            if p.y() < miny {
86                miny = p.y();
87            }
88            if p.x() > maxx {
89                maxx = p.x();
90            }
91            if p.y() > maxy {
92                maxy = p.y();
93            }
94            count += 1;
95        }
96        if count >= 2 {
97            Self::new(&Point2D { x: minx, y: miny }, &Point2D { x: maxx, y: maxy })
98        } else {
99            Err(ErrorKind::TooFewPoints)
100        }
101    }
102    /// Creates a new BoundingBox2D which contains all the given positions
103    pub fn from_into_iterator<It2D, P>(source: It2D) -> Result<BoundingBox2D>
104    where
105        It2D: IntoIterator<Item = P>,
106        P: Is2D + Sized,
107    {
108        let mut count = 0;
109
110        let mut minx: f64 = 0.0;
111        let mut miny: f64 = 0.0;
112        let mut maxx: f64 = 0.0;
113        let mut maxy: f64 = 0.0;
114
115        for p in source {
116            if count == 0 {
117                minx = p.x();
118                miny = p.y();
119                maxx = p.x();
120                maxy = p.y();
121                count += 1;
122                continue;
123            }
124            if p.x() < minx {
125                minx = p.x();
126            }
127            if p.y() < miny {
128                miny = p.y();
129            }
130            if p.x() > maxx {
131                maxx = p.x();
132            }
133            if p.y() > maxy {
134                maxy = p.y();
135            }
136            count += 1;
137        }
138        if count >= 2 {
139            Self::new(&Point2D { x: minx, y: miny }, &Point2D { x: maxx, y: maxy })
140        } else {
141            Err(ErrorKind::TooFewPoints)
142        }
143    }
144    /// Returns the minimum position of the bounding box
145    pub fn min_p(&self) -> Point2D {
146        self.min.clone()
147    }
148    /// Returns the maximum position of the bounding box
149    pub fn max_p(&self) -> Point2D {
150        self.max.clone()
151    }
152    /// Returns the size the bounding box within the x-dimension
153    pub fn size_x(&self) -> Positive {
154        Positive::new((self.max.x() - self.min.x()).abs()).unwrap() //safe since constrain enforced on construction
155    }
156    /// Returns the size the bounding box within the y-dimension
157    pub fn size_y(&self) -> Positive {
158        Positive::new((self.max.y() - self.min.y()).abs()).unwrap() //safe since constrain enforced on construction
159    }
160    /// Returns the sizes of the bounding box
161    pub fn sizes(&self) -> [Positive; 2] {
162        [self.size_x(), self.size_y()]
163    }
164    /// Returns the center of the bounding box
165    pub fn center_bb(&self) -> Point2D {
166        Point2D {
167            x: self.min.x() + (self.max.x() - self.min.x()) / 2.0,
168            y: self.min.y() + (self.max.y() - self.min.y()) / 2.0,
169        }
170    }
171    /// Tests whether this bounding box is within the other
172    pub fn is_inside(&self, other: &BoundingBox2D) -> bool {
173        self.min.x() > other.min.x()
174            && self.min.y() > other.min.y()
175            && self.max.x() < other.max.x()
176            && self.max.y() < other.max.y()
177    }
178    /// Tests whether this bounding box contains a position
179    pub fn contains<P>(&self, other: &P) -> bool
180    where
181        Self: Sized,
182        P: Is2D,
183    {
184        other.x() > self.min.x()
185            && other.x() < self.max.x()
186            && other.y() > self.min.y()
187            && other.y() < self.max.y()
188    }
189    /// Tests whether this bounding box contains the other
190    pub fn has_inside(&self, other: &BoundingBox2D) -> bool {
191        self.min.x() < other.min.x()
192            && self.min.y() < other.min.y()
193            && self.max.x() > other.max.x()
194            && self.max.y() > other.max.y()
195    }
196    /// Tests whether this bounding box and the other overlap in any way
197    pub fn collides_with(&self, other: &BoundingBox2D) -> bool {
198        2.0 * (self.center_bb().x - other.center_bb().x).abs()
199            < ((self.size_x() + other.size_x()).get())
200            && 2.0 * (self.center_bb().y - other.center_bb().y).abs()
201                < ((self.size_y() + other.size_y()).get())
202    }
203    /// Tests whether this bounding box crosses a certain x value
204    pub fn crossing_x_value(&self, x: f64) -> bool {
205        self.min.x() < x && self.max.x() > x
206    }
207    /// Tests whether this bounding box crosses a certain y value
208    pub fn crossing_y_value(&self, y: f64) -> bool {
209        self.min.y() < y && self.max.y() > y
210    }
211    /// Returns the corner points of the bounding box
212    pub fn corners(&self) -> [Point2D; 4] {
213        [
214            Point2D::new(self.min.x(), self.min.y()),
215            Point2D::new(self.min.x(), self.max.y()),
216            Point2D::new(self.max.x(), self.min.y()),
217            Point2D::new(self.max.x(), self.max.y()),
218        ]
219    }
220    /// Returns the distance to another Is2D
221    pub fn distance<P>(&self, other: &P) -> NonNegative
222    where
223        P: Is2D,
224    {
225        let sqr_dist = self.sqr_distance(other).get();
226        NonNegative::new(sqr_dist.sqrt()).unwrap()
227    }
228    /// Returns the square distance to another Is2D
229    pub fn sqr_distance<P>(&self, other: &P) -> NonNegative
230    where
231        P: Is2D,
232    {
233        let dx = max_f64_3(
234            self.min_p().x() - other.x(),
235            0.0,
236            other.x() - self.max_p().x(),
237        );
238        let dy = max_f64_3(
239            self.min_p().y() - other.y(),
240            0.0,
241            other.y() - self.max_p().y(),
242        );
243        NonNegative::new(dx * dx + dy * dy).unwrap()
244    }
245}
246
247//------------------------------------------------------------------------------
248
249impl Default for BoundingBox2D {
250    fn default() -> Self {
251        BoundingBox2D {
252            min: Point2D { x: -0.5, y: -0.5 },
253            max: Point2D { x: 0.5, y: 0.5 },
254        }
255    }
256}
257
258impl IsND for BoundingBox2D {
259    fn n_dimensions() -> usize {
260        2
261    }
262
263    fn position_nd(&self, dimension: usize) -> Result<f64> {
264        self.center_bb().position_nd(dimension)
265    }
266}
267
268impl Is2D for BoundingBox2D {
269    #[inline(always)]
270    fn x(&self) -> f64 {
271        self.center_bb().x()
272    }
273
274    #[inline(always)]
275    fn y(&self) -> f64 {
276        self.center_bb().y()
277    }
278}
279
280impl IsMovable2D for BoundingBox2D {
281    fn move_by(&mut self, x: f64, y: f64) {
282        self.min.move_by(x, y);
283        self.max.move_by(x, y);
284    }
285}
286
287impl HasBoundingBox2D for BoundingBox2D {
288    fn bounding_box(&self) -> BoundingBox2D {
289        BoundingBox2D::new(&self.min, &self.max).unwrap() // safe
290    }
291}
292
293impl HasBoundingBox2DMaybe for BoundingBox2D {
294    fn bounding_box_maybe(&self) -> Result<BoundingBox2D> {
295        Ok(self.bounding_box())
296    }
297}
298
299impl HasDistanceTo<BoundingBox2D> for BoundingBox2D {
300    fn sqr_distance(&self, other: &BoundingBox2D) -> NonNegative {
301        let mut dx = 0.0;
302        let mut dy = 0.0;
303
304        if other.max_p().x() < self.min_p().x() {
305            dx = other.max_p().x() - self.min_p().x();
306        } else if other.min_p().x() > self.max_p().x() {
307            dx = other.min_p().x() - self.max_p().x();
308        }
309
310        if other.max_p().y() < self.min_p().y() {
311            dy = other.max_p().y() - self.min_p().y();
312        } else if other.min_p().y() > self.max_p().y() {
313            dy = other.min_p().y() - self.max_p().y();
314        }
315
316        NonNegative::new(dx * dx + dy * dy).unwrap()
317    }
318}
319
320impl IsScalable for BoundingBox2D {
321    fn scale(&mut self, factor: Positive) {
322        let c = self.center_bb();
323        let min_x = c.x - (0.5 * factor.get() * self.size_x().get());
324        let max_x = c.x + (0.5 * factor.get() * self.size_x().get());
325        let min_y = c.y - (0.5 * factor.get() * self.size_y().get());
326        let max_y = c.y + (0.5 * factor.get() * self.size_y().get());
327
328        self.min.set_xy(min_x, min_y);
329        self.max.set_xy(max_x, max_y);
330    }
331}
332
333impl IsMergeable for BoundingBox2D {
334    fn consume(&mut self, other: Self) {
335        let (mut min_x, mut min_y) = (self.min.x(), self.min.y());
336        let (mut max_x, mut max_y) = (self.max.x(), self.max.y());
337
338        if other.min.x() < min_x {
339            min_x = other.min.x()
340        }
341        if other.min.y() < min_y {
342            min_y = other.min.y()
343        }
344
345        if other.max.x() > max_x {
346            max_x = other.max.x()
347        }
348        if other.max.y() > max_y {
349            max_y = other.max.y()
350        }
351
352        self.min.set_xy(min_x, min_y);
353        self.max.set_xy(max_x, max_y);
354    }
355
356    fn combine(&self, other: &Self) -> Self {
357        let (mut min_x, mut min_y) = (self.min.x(), self.min.y());
358        let (mut max_x, mut max_y) = (self.max.x(), self.max.y());
359
360        if other.min.x() < min_x {
361            min_x = other.min.x()
362        }
363        if other.min.y() < min_y {
364            min_y = other.min.y()
365        }
366
367        if other.max.x() > max_x {
368            max_x = other.max.x()
369        }
370        if other.max.y() > max_y {
371            max_y = other.max.y()
372        }
373
374        let min = Point2D::new(min_x, min_y);
375        let max = Point2D::new(max_x, max_y);
376
377        BoundingBox2D { min, max }
378    }
379}