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}