phys_geom/
aabb2.rs

1// Copyright (C) 2020-2025 phys-geom authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::ops::{Add, AddAssign, Sub, SubAssign};
16
17use crate::math::{Point2, Real, Vec2};
18
19/// 2D Axis-Aligned Bounding Box (AABB) implementation.
20///
21/// This module provides functionality for working with axis-aligned bounding boxes in 2D space.
22/// AABBs are fundamental for spatial partitioning, collision detection, and various geometric
23/// algorithms. The implementation supports both f32 and f64 precision through feature flags.
24///
25/// # Examples
26///
27/// ```
28/// use phys_geom::{Aabb2, math::Point2};
29///
30/// // Create an AABB from two points
31/// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(10.0, 5.0));
32///
33/// // Get the center and size
34/// let center = aabb.center();
35/// let size = aabb.size();
36///
37/// // Check if two AABBs overlap
38/// let other = Aabb2::new(Point2::new(5.0, 2.0), Point2::new(15.0, 8.0));
39/// let overlaps = aabb.overlap_test(&other);
40/// ```
41/// 2D Axis-Aligned Bounding Box.
42///
43/// An AABB represents a rectangular region in 2D space defined by its minimum and maximum
44/// coordinates. The box is axis-aligned, meaning its edges are parallel to the coordinate axes.
45///
46/// # Representation
47///
48/// The AABB is stored using two `Point2` values:
49/// - `min`: The bottom-left corner of the box
50/// - `max`: The top-right corner of the box
51///
52/// # Invariants
53///
54/// For a valid AABB, `min.x <= max.x` and `min.y <= max.y` must hold true.
55/// The `new_unchecked` method enforces this with debug assertions.
56#[derive(Copy, Clone, Default, Debug)]
57#[repr(C)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Aabb2 {
60    min: Point2,
61    max: Point2,
62}
63
64impl Aabb2 {
65    /// Zero-sized AABB located at the origin.
66    ///
67    /// Both min and max points are at (0, 0), representing a degenerate box with zero area.
68    pub const ZERO: Aabb2 = Self {
69        min: Point2::new(0.0, 0.0),
70        max: Point2::new(0.0, 0.0),
71    };
72
73    /// Creates a new AABB from two points.
74    ///
75    /// The AABB will be the smallest axis-aligned rectangle that contains both points.
76    /// This method automatically determines which point should be the min and max corners.
77    ///
78    /// # Arguments
79    ///
80    /// * `p1` - First point
81    /// * `p2` - Second point
82    ///
83    /// # Returns
84    ///
85    /// A new AABB that contains both input points.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use phys_geom::{Aabb2, math::Point2};
91    ///
92    /// let p1 = Point2::new(5.0, 10.0);
93    /// let p2 = Point2::new(1.0, 3.0);
94    /// let aabb = Aabb2::new(p1, p2);
95    ///
96    /// assert_eq!(aabb.min(), Point2::new(1.0, 3.0));
97    /// assert_eq!(aabb.max(), Point2::new(5.0, 10.0));
98    /// ```
99    #[inline]
100    #[must_use]
101    pub fn new(p1: Point2, p2: Point2) -> Aabb2 {
102        Aabb2 {
103            min: Point2::new(Real::min(p1.x, p2.x), Real::min(p1.y, p2.y)),
104            max: Point2::new(Real::max(p1.x, p2.x), Real::max(p1.y, p2.y)),
105        }
106    }
107
108    /// Creates a new AABB from explicit min and max points.
109    ///
110    /// Unlike the `new` method, this function assumes the caller has already determined
111    /// which point is the minimum and which is the maximum. This is more efficient when
112    /// you already know the min/max relationship.
113    ///
114    /// # Safety
115    ///
116    /// The caller must ensure that `min.x <= max.x` and `min.y <= max.y`.
117    /// Debug assertions will catch violations in debug builds.
118    ///
119    /// # Arguments
120    ///
121    /// * `min` - The minimum corner (bottom-left) of the AABB
122    /// * `max` - The maximum corner (top-right) of the AABB
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use phys_geom::{Aabb2, math::Point2};
128    ///
129    /// let min = Point2::new(0.0, 0.0);
130    /// let max = Point2::new(10.0, 5.0);
131    /// let aabb = Aabb2::new_unchecked(min, max);
132    /// ```
133    #[inline]
134    #[must_use]
135    pub fn new_unchecked(min: Point2, max: Point2) -> Self {
136        debug_assert!(min.x <= max.x, "min > max, min = {min},max = {max}");
137        debug_assert!(min.y <= max.y, "min > max, min = {min},max = {max}");
138        Self { min, max }
139    }
140
141    /// Creates an AABB from an array of 4 floating-point values.
142    ///
143    /// The array elements should be in the order: [min_x, min_y, max_x, max_y].
144    /// This method is useful for interoperability with other systems that
145    /// represent AABBs as flat arrays.
146    ///
147    /// # Arguments
148    ///
149    /// * `values` - Array of 4 values: [min_x, min_y, max_x, max_y]
150    ///
151    /// # Returns
152    ///
153    /// A new AABB created from the array values.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use phys_geom::{Aabb2, math::Point2};
159    ///
160    /// let values = [0.0, 0.0, 10.0, 5.0];
161    /// let aabb = Aabb2::from_array_unchecked(values);
162    ///
163    /// assert_eq!(aabb.min(), Point2::new(0.0, 0.0));
164    /// assert_eq!(aabb.max(), Point2::new(10.0, 5.0));
165    /// ```
166    #[inline]
167    #[must_use]
168    pub fn from_array_unchecked(values: [Real; 4]) -> Self {
169        Aabb2 {
170            min: Point2::new(values[0], values[1]),
171            max: Point2::new(values[2], values[3]),
172        }
173    }
174
175    /// Returns the minimum corner of the AABB.
176    ///
177    /// This is the bottom-left corner of the bounding box, containing the smallest
178    /// x and y coordinates within the AABB.
179    ///
180    /// # Returns
181    ///
182    /// The minimum point of the AABB.
183    #[inline]
184    #[must_use]
185    pub fn min(&self) -> Point2 {
186        self.min
187    }
188
189    /// Returns the maximum corner of the AABB.
190    ///
191    /// This is the top-right corner of the bounding box, containing the largest
192    /// x and y coordinates within the AABB.
193    ///
194    /// # Returns
195    ///
196    /// The maximum point of the AABB.
197    #[inline]
198    #[must_use]
199    pub fn max(&self) -> Point2 {
200        self.max
201    }
202
203    /// Expands the AABB to include the specified point.
204    ///
205    /// If the point is already inside the AABB, this method has no effect.
206    /// Otherwise, the AABB is expanded to include the point.
207    ///
208    /// # Arguments
209    ///
210    /// * `p` - The point to include in the AABB
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use phys_geom::{Aabb2, math::Point2};
216    ///
217    /// let mut aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(5.0, 5.0));
218    /// aabb.grow(Point2::new(10.0, -2.0));
219    ///
220    /// assert_eq!(aabb.min(), Point2::new(0.0, -2.0));
221    /// assert_eq!(aabb.max(), Point2::new(10.0, 5.0));
222    /// ```
223    #[inline]
224    pub fn grow(&mut self, p: Point2) {
225        self.min = self.min.inf(&p);
226        self.max = self.max.sup(&p);
227    }
228
229    /// Returns the four corner points of the AABB.
230    ///
231    /// The corners are returned in counter-clockwise order starting from the minimum corner:
232    /// 1. Bottom-left (min.x, min.y)
233    /// 2. Top-left (min.x, max.y)
234    /// 3. Top-right (max.x, max.y)
235    /// 4. Bottom-right (max.x, min.y)
236    ///
237    /// # Returns
238    ///
239    /// Array of 4 points representing the corners of the AABB.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// use phys_geom::{Aabb2, math::Point2};
245    ///
246    /// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(10.0, 5.0));
247    /// let corners = aabb.corners();
248    ///
249    /// assert_eq!(corners[0], Point2::new(0.0, 0.0));  // bottom-left
250    /// assert_eq!(corners[1], Point2::new(0.0, 5.0));  // top-left
251    /// assert_eq!(corners[2], Point2::new(10.0, 5.0)); // top-right
252    /// assert_eq!(corners[3], Point2::new(10.0, 0.0)); // bottom-right
253    /// ```
254    #[inline]
255    #[must_use]
256    pub fn corners(&self) -> [Point2; 4] {
257        let min = self.min;
258        let max = self.max;
259        [
260            min,
261            Point2::new(min.x, max.y),
262            max,
263            Point2::new(max.x, min.y),
264        ]
265    }
266
267    /// Expands the AABB by adding a margin on all sides.
268    ///
269    /// The margin is added to both sides of the AABB, effectively increasing its
270    /// size by 2 * margin in each dimension. This is useful for creating buffer zones
271    /// around bounding boxes.
272    ///
273    /// # Arguments
274    ///
275    /// * `margin` - The margin to add on all sides (must be non-negative)
276    ///
277    /// # Returns
278    ///
279    /// A new AABB expanded by the specified margin.
280    ///
281    /// # Panics
282    ///
283    /// In debug builds, this method panics if margin components are negative.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
289    ///
290    /// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(10.0, 5.0));
291    /// let expanded = aabb.with_margin(Vec2::new(1.0, 0.5));
292    ///
293    /// assert_eq!(expanded.min(), Point2::new(-1.0, -0.5));
294    /// assert_eq!(expanded.max(), Point2::new(11.0, 5.5));
295    /// ```
296    #[inline]
297    #[must_use]
298    pub fn with_margin(&self, margin: Vec2) -> Self {
299        debug_assert!(margin.x >= 0.0 && margin.y >= 0.0);
300        let min = self.min - margin;
301        let max = self.max + margin;
302        Self::new_unchecked(min, max)
303    }
304
305    /// Returns the center point of the AABB.
306    ///
307    /// The center is calculated as the midpoint between the min and max corners.
308    /// This method automatically adapts to the current floating-point precision (f32 or f64).
309    ///
310    /// # Returns
311    ///
312    /// The center point of the AABB.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use phys_geom::{Aabb2, math::Point2};
318    ///
319    /// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(10.0, 5.0));
320    /// let center = aabb.center();
321    ///
322    /// assert_eq!(center, Point2::new(5.0, 2.5));
323    /// ```
324    #[inline]
325    #[must_use]
326    pub fn center(&self) -> Point2 {
327        const HALF: Real = 0.5;
328        Point2::from((self.min.coords + self.max.coords) * HALF)
329    }
330
331    /// Returns the size (dimensions) of the AABB.
332    ///
333    /// The size is calculated as max - min, giving the width and height of the AABB.
334    /// Note that this method returns zero for degenerate AABBs where min equals max.
335    ///
336    /// # Returns
337    ///
338    /// A vector representing the width (x) and height (y) of the AABB.
339    ///
340    /// # Examples
341    ///
342    /// ```
343    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
344    ///
345    /// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(10.0, 5.0));
346    /// let size = aabb.size();
347    ///
348    /// assert_eq!(size, Vec2::new(10.0, 5.0));
349    /// ```
350    #[inline]
351    #[must_use]
352    pub fn size(&self) -> Vec2 {
353        self.max - self.min
354    }
355
356    /// Tests if this AABB overlaps with another AABB.
357    ///
358    /// Two AABBs overlap if they share any common area. This method performs
359    /// a separating axis test along both the x and y axes.
360    ///
361    /// # Arguments
362    ///
363    /// * `rhs` - The other AABB to test against
364    ///
365    /// # Returns
366    ///
367    /// `true` if the AABBs overlap, `false` otherwise.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use phys_geom::{Aabb2, math::Point2};
373    ///
374    /// let aabb1 = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(5.0, 5.0));
375    /// let aabb2 = Aabb2::new(Point2::new(3.0, 3.0), Point2::new(8.0, 8.0));
376    /// let aabb3 = Aabb2::new(Point2::new(10.0, 10.0), Point2::new(15.0, 15.0));
377    ///
378    /// assert!(aabb1.overlap_test(&aabb2));  // overlapping
379    /// assert!(!aabb1.overlap_test(&aabb3)); // not overlapping
380    /// ```
381    #[inline]
382    #[must_use]
383    pub fn overlap_test(&self, rhs: &Aabb2) -> bool {
384        Self::overlap_test_1d(self.min[0], self.max[0], rhs.min[0], rhs.max[0])
385            && Self::overlap_test_1d(self.min[1], self.max[1], rhs.min[1], rhs.max[1])
386    }
387
388    /// Tests overlap between two 1D intervals.
389    ///
390    /// This is a helper method that checks if two intervals [min0, max0] and [min1, max1]
391    /// overlap on a single axis. Two intervals overlap if the start of one is less than
392    /// the end of the other, and vice versa.
393    ///
394    /// # Arguments
395    ///
396    /// * `min0` - Start of first interval
397    /// * `max0` - End of first interval
398    /// * `min1` - Start of second interval
399    /// * `max1` - End of second interval
400    ///
401    /// # Returns
402    ///
403    /// `true` if the intervals overlap, `false` otherwise.
404    #[inline]
405    fn overlap_test_1d(min0: Real, max0: Real, min1: Real, max1: Real) -> bool {
406        max0 > min1 && min0 < max1
407    }
408}
409
410impl Add<Vec2> for Aabb2 {
411    type Output = Aabb2;
412
413    /// Translates the AABB by adding a vector.
414    ///
415    /// This operation moves the entire AABB by the specified vector,
416    /// preserving its size and shape.
417    ///
418    /// # Arguments
419    ///
420    /// * `rhs` - The translation vector
421    ///
422    /// # Returns
423    ///
424    /// A new AABB translated by the vector.
425    ///
426    /// # Examples
427    ///
428    /// ```
429    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
430    ///
431    /// let aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(5.0, 5.0));
432    /// let translated = aabb + Vec2::new(10.0, 5.0);
433    ///
434    /// assert_eq!(translated.min(), Point2::new(10.0, 5.0));
435    /// assert_eq!(translated.max(), Point2::new(15.0, 10.0));
436    /// ```
437    #[inline]
438    fn add(self, rhs: Vec2) -> Self::Output {
439        let min = self.min + rhs;
440        let max = self.max + rhs;
441        Aabb2::new_unchecked(min, max)
442    }
443}
444
445impl Sub<Vec2> for Aabb2 {
446    type Output = Aabb2;
447
448    /// Translates the AABB by subtracting a vector.
449    ///
450    /// This operation moves the entire AABB by the negative of the specified vector,
451    /// preserving its size and shape.
452    ///
453    /// # Arguments
454    ///
455    /// * `rhs` - The translation vector to subtract
456    ///
457    /// # Returns
458    ///
459    /// A new AABB translated by the negative vector.
460    ///
461    /// # Examples
462    ///
463    /// ```
464    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
465    ///
466    /// let aabb = Aabb2::new(Point2::new(10.0, 5.0), Point2::new(15.0, 10.0));
467    /// let translated = aabb - Vec2::new(10.0, 5.0);
468    ///
469    /// assert_eq!(translated.min(), Point2::new(0.0, 0.0));
470    /// assert_eq!(translated.max(), Point2::new(5.0, 5.0));
471    /// ```
472    #[inline]
473    fn sub(self, rhs: Vec2) -> Self::Output {
474        let min = self.min - rhs;
475        let max = self.max - rhs;
476        Aabb2::new_unchecked(min, max)
477    }
478}
479
480impl AddAssign<Vec2> for Aabb2 {
481    /// Translates the AABB in-place by adding a vector.
482    ///
483    /// This operation moves the entire AABB by the specified vector,
484    /// modifying the original AABB.
485    ///
486    /// # Arguments
487    ///
488    /// * `rhs` - The translation vector
489    ///
490    /// # Examples
491    ///
492    /// ```
493    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
494    ///
495    /// let mut aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(5.0, 5.0));
496    /// aabb += Vec2::new(10.0, 5.0);
497    ///
498    /// assert_eq!(aabb.min(), Point2::new(10.0, 5.0));
499    /// assert_eq!(aabb.max(), Point2::new(15.0, 10.0));
500    /// ```
501    #[inline]
502    fn add_assign(&mut self, rhs: Vec2) {
503        self.min += rhs;
504        self.max += rhs;
505    }
506}
507
508impl SubAssign<Vec2> for Aabb2 {
509    /// Translates the AABB in-place by subtracting a vector.
510    ///
511    /// This operation moves the entire AABB by the negative of the specified vector,
512    /// modifying the original AABB.
513    ///
514    /// # Arguments
515    ///
516    /// * `rhs` - The translation vector to subtract
517    ///
518    /// # Examples
519    ///
520    /// ```
521    /// use phys_geom::{Aabb2, math::{Point2, Vec2}};
522    ///
523    /// let mut aabb = Aabb2::new(Point2::new(10.0, 5.0), Point2::new(15.0, 10.0));
524    /// aabb -= Vec2::new(10.0, 5.0);
525    ///
526    /// assert_eq!(aabb.min(), Point2::new(0.0, 0.0));
527    /// assert_eq!(aabb.max(), Point2::new(5.0, 5.0));
528    /// ```
529    #[inline]
530    fn sub_assign(&mut self, rhs: Vec2) {
531        self.min -= rhs;
532        self.max -= rhs;
533    }
534}
535
536#[cfg(test)]
537mod tests {
538    use approx::assert_relative_eq;
539
540    use super::*;
541
542    #[test]
543    fn test_aabb_grow() {
544        let mut aabb = Aabb2::new(Point2::new(0.0, 0.0), Point2::new(1.0, 1.0));
545        aabb.grow(Point2::new(-1.0, 2.0));
546        assert_relative_eq!(aabb.min(), Point2::new(-1.0, 0.0));
547        assert_relative_eq!(aabb.max(), Point2::new(1.0, 2.0));
548    }
549
550    #[test]
551    fn test_aabb_corners() {
552        let aabb = Aabb2::new(Point2::new(-1.0, -2.0), Point2::new(1.0, 2.0));
553        let corners = aabb.corners();
554        assert_relative_eq!(corners[0], Point2::new(-1.0, -2.0));
555        assert_relative_eq!(corners[1], Point2::new(-1.0, 2.0));
556        assert_relative_eq!(corners[2], Point2::new(1.0, 2.0));
557        assert_relative_eq!(corners[3], Point2::new(1.0, -2.0));
558    }
559}