phys_geom/
aabb3.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
15//! Axis-Aligned Bounding Box (AABB) operations in 3D space.
16//!
17//! This module provides comprehensive functionality for working with axis-aligned bounding boxes,
18//! which are fundamental components in physics engines, collision detection, and spatial
19//! partitioning systems.
20//!
21//! # Overview
22//!
23//! An AABB is a rectangular box whose faces are aligned with the coordinate axes. It's defined
24//! by two points: a minimum corner (min) and a maximum corner (max). The AABB is the smallest
25//! box that contains all specified points and is axis-aligned.
26//!
27//! # Key Features
28//!
29//! - **Construction**: Multiple ways to create AABBs from points, shapes, or raw data
30//! - **Ray Casting**: Efficient ray-AABB intersection testing for collision detection
31//! - **Transformations**: Support for transforming AABBs with isometries
32//! - **Operations**: Union, intersection, expansion, and other set operations
33//! - **Validation**: Runtime assertions for valid AABB constraints
34//! - **Serialization**: Optional serde support for persistence
35//!
36//! # Common Operations
37//!
38//! ```rust
39//! use phys_geom::{Aabb3, math::Point3, math::Vec3, math::Isometry3};
40//!
41//! // Create an AABB from two points
42//! let aabb = Aabb3::new(Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, 1.0, 1.0));
43//!
44//! // Get properties
45//! let center = aabb.center();
46//! let extents = aabb.get_extents();
47//!
48//! // Transform the AABB
49//! let isometry = Isometry3::default();
50//! let transformed_aabb = aabb.transform(isometry);
51//!
52//! // Expand the AABB
53//! let expanded_aabb = aabb.with_margin(Vec3::new(0.1, 0.1, 0.1));
54//! ```
55//!
56//! # Ray Casting
57//!
58//! The module provides efficient ray-AABB intersection testing:
59//!
60//! ```rust
61//! use phys_geom::{Aabb3, Ray, math::Point3, math::Vec3};
62//!
63//! let aabb = Aabb3::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0));
64//! let direction = Vec3::x_axis();
65//! let ray = Ray::new(Point3::new(-1.0, 0.5, 0.5), direction);
66//!
67//! // Check if ray intersects AABB
68//! let intersects = aabb.raycast(ray, 10.0);
69//! ```
70//!
71//! # Performance Considerations
72//!
73//! - AABB operations are generally O(1) time complexity
74//! - Ray casting uses efficient slab-based algorithms
75//! - Transform operations require computing all 8 corners (can be optimized)
76//! - Memory usage is minimal (just two 3D points)
77//!
78//! # Special Values
79//!
80//! - [`Aabb3::ANTI_INFINITE`]: An empty AABB used as initial value for union operations
81//! - [`Aabb3::ZERO`]: A zero-sized AABB at the origin
82
83use std::ops::{Add, AddAssign, Sub, SubAssign};
84
85use na::point;
86
87use super::Interval;
88use crate::aabb2::Aabb2;
89use crate::math::{Isometry3, Point3, Real, Vec3};
90use crate::shape::*;
91use crate::Ray;
92
93/// An axis-aligned bounding box in 3D space.
94///
95/// An AABB is the smallest rectangular box that contains a set of points, with its faces
96/// aligned to the coordinate axes. It's defined by two 3D points:
97/// - `min`: The corner with minimum coordinate values
98/// - `max`: The corner with maximum coordinate values
99///
100/// AABBs are fundamental in physics engines for:
101/// - Broad-phase collision detection
102/// - Spatial partitioning and culling
103/// - Bounding volume hierarchies
104/// - Ray casting and intersection testing
105///
106/// # Examples
107///
108/// ```rust
109/// use phys_geom::{Aabb3, math::Point3};
110///
111/// // Create an AABB from two points
112/// let aabb = Aabb3::new(
113///     Point3::new(-1.0, -1.0, -1.0),
114///     Point3::new(1.0, 1.0, 1.0)
115/// );
116///
117/// // Use special constants
118/// let empty_aabb = Aabb3::ANTI_INFINITE;
119/// let zero_aabb = Aabb3::ZERO;
120/// ```
121///
122/// # Invariants
123///
124/// For a valid AABB, the following must hold: `min.x <= max.x`, `min.y <= max.y`, `min.z <= max.z`
125///
126/// # Memory Layout
127///
128/// The struct uses `#[repr(C)]` for FFI compatibility and contains exactly two Point3 values.
129#[derive(Copy, Clone, Default, Debug, PartialEq)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[repr(C)]
132pub struct Aabb3 {
133    /// The corner with minimum coordinate values
134    min: Point3,
135    /// The corner with maximum coordinate values
136    max: Point3,
137}
138
139impl Aabb3 {
140    /// An empty/invalid AABB used as initial value for union operations.
141    ///
142    /// This special AABB has `min = [MAX, MAX, MAX]` and `max = [MIN, MIN, MIN]`,
143    /// making it the identity element for union operations. Any AABB unioned with
144    /// ANTI_INFINITE will result in the original AABB.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use phys_geom::{Aabb3, math::Point3};
150    ///
151    /// let mut aabb = Aabb3::ANTI_INFINITE;
152    /// aabb.grow(Point3::new(1.0, 2.0, 3.0));
153    /// aabb.grow(Point3::new(-1.0, -2.0, -3.0));
154    ///
155    /// assert_eq!(aabb.min(), Point3::new(-1.0, -2.0, -3.0));
156    /// assert_eq!(aabb.max(), Point3::new(1.0, 2.0, 3.0));
157    /// ```
158    pub const ANTI_INFINITE: Self = Self {
159        min: point![Real::MAX, Real::MAX, Real::MAX],
160        max: point![Real::MIN, Real::MIN, Real::MIN],
161    };
162    /// A zero-sized AABB located at the origin.
163    ///
164    /// Both min and max points are at the origin (0, 0, 0), resulting in an AABB
165    /// with zero volume. This is useful as a starting point for incremental
166    /// bounding box computation.
167    pub const ZERO: Aabb3 = Self {
168        min: point![0.0, 0.0, 0.0],
169        max: point![0.0, 0.0, 0.0],
170    };
171
172    #[inline]
173    #[must_use]
174    pub fn new(p1: impl Into<[Real; 3]>, p2: impl Into<[Real; 3]>) -> Aabb3 {
175        let p1: Point3 = p1.into().into();
176        let p2: Point3 = p2.into().into();
177        Aabb3 {
178            min: Point3::new(
179                Real::min(p1.x, p2.x),
180                Real::min(p1.y, p2.y),
181                Real::min(p1.z, p2.z),
182            ),
183            max: Point3::new(
184                Real::max(p1.x, p2.x),
185                Real::max(p1.y, p2.y),
186                Real::max(p1.z, p2.z),
187            ),
188        }
189    }
190
191    /// Create AABB bound with min and max point.
192    /// Different from `new` method, the `new_unchecked` won't compare min and max point, and will
193    /// assign them to AABB directly. It means user should be ensure that min point is
194    /// `actually` smaller than max point.
195    #[inline]
196    #[must_use]
197    pub fn new_unchecked(min: impl Into<Point3>, max: impl Into<Point3>) -> Self {
198        let min = min.into();
199        let max = max.into();
200        #[cfg(debug_assertions)]
201        if min != Self::ANTI_INFINITE.min || max != Self::ANTI_INFINITE.max {
202            // If not in anti-infinite state, we should check min and max.
203            let error_msg = format!("min > max, min = {min}, max = {max}");
204            debug_assert!(min.x <= max.x, "{}", error_msg);
205            debug_assert!(min.y <= max.y, "{}", error_msg);
206            debug_assert!(min.z <= max.z, "{}", error_msg);
207        }
208        Self { min, max }
209    }
210
211    //first 3 element is min, later 3 elements is max
212    #[inline]
213    #[must_use]
214    pub fn from_array_unchecked(values: [Real; 6]) -> Self {
215        Aabb3 {
216            min: Point3::new(values[0], values[1], values[2]),
217            max: Point3::new(values[3], values[4], values[5]),
218        }
219    }
220
221    /// Create AABB from center and extents.
222    #[inline]
223    #[must_use]
224    pub fn from_center_extents(center: impl Into<Point3>, extents: impl Into<Vec3>) -> Self {
225        let center = center.into();
226        let extents = extents.into();
227        Aabb3::new_unchecked(center - extents, center + extents)
228    }
229
230    /// Returns the minimum point of the AABB.
231    #[inline]
232    #[must_use]
233    pub fn min(&self) -> Point3 {
234        self.min
235    }
236
237    /// Returns the maximum point of the AABB.
238    #[inline]
239    #[must_use]
240    pub fn max(&self) -> Point3 {
241        self.max
242    }
243
244    /// Get the interval of the AABB along the given axis.
245    ///
246    /// # Panics
247    ///
248    /// Panics if `axis` is greater than 2.
249    #[inline]
250    #[must_use]
251    pub fn get_interval(&self, axis: usize) -> Interval {
252        debug_assert!(axis < 3);
253        Interval::new(self.min[axis], self.max[axis])
254    }
255
256    /// Set the minimum value of the AABB at the given axis.
257    ///
258    /// # Panics
259    ///
260    /// Panics if the set value is large than `max[axis]`
261    #[inline]
262    pub fn set_min_at(&mut self, axis: usize, value: Real) {
263        debug_assert!(axis < 3);
264        assert!(value <= self.max[axis]);
265        self.min[axis] = value;
266    }
267
268    /// Set the maximum value of the AABB at the given axis.
269    ///
270    /// # Panics
271    ///
272    /// Panics if the set value is smaller than `min[axis]`
273    #[inline]
274    pub fn set_max_at(&mut self, axis: usize, value: Real) {
275        debug_assert!(axis < 3);
276        assert!(self.min[axis] <= value);
277        self.max[axis] = value;
278    }
279
280    /// Grow the aabb to include the given point.
281    #[inline]
282    pub fn grow(&mut self, p: impl Into<Point3>) {
283        let p = p.into();
284        self.min = self.min.inf(&p);
285        self.max = self.max.sup(&p);
286    }
287
288    #[inline]
289    pub fn merge(&mut self, other: &Self) {
290        self.min = self.min.inf(&other.min);
291        self.max = self.max.sup(&other.max);
292    }
293
294    #[inline]
295    pub fn expand(&mut self, margin: Real) {
296        self.min -= Vec3::new(margin, margin, margin);
297        self.max += Vec3::new(margin, margin, margin);
298    }
299
300    /// Offset the AABB by the given vector.
301    #[inline]
302    pub fn offset(&mut self, offset: impl Into<[Real; 3]>) {
303        let offset = Vec3::from(offset.into());
304        self.min += offset;
305        self.max += offset;
306    }
307
308    /// Offset the AABB by the given vector, returning the modified AABB.
309    #[inline]
310    pub fn with_offset(mut self, offset: impl Into<[Real; 3]>) -> Self {
311        self.offset(offset);
312        self
313    }
314
315    #[inline]
316    #[must_use]
317    pub fn corners(&self) -> [Point3; 8] {
318        let min = self.min;
319        let max = self.max;
320
321        // Use consistent coordinate combinations for all 8 corners
322        [
323            min,                              // (min.x, min.y, min.z)
324            Point3::new(min.x, min.y, max.z), // (min.x, min.y, max.z)
325            Point3::new(min.x, max.y, min.z), // (min.x, max.y, min.z)
326            Point3::new(min.x, max.y, max.z), // (min.x, max.y, max.z)
327            Point3::new(max.x, min.y, min.z), // (max.x, min.y, min.z)
328            Point3::new(max.x, min.y, max.z), // (max.x, min.y, max.z)
329            Point3::new(max.x, max.y, min.z), // (max.x, max.y, min.z)
330            max,                              // (max.x, max.y, max.z)
331        ]
332    }
333
334    /// Extend AABB with margin.
335    #[inline]
336    #[must_use]
337    pub fn with_margin(&self, margin: impl Into<[Real; 3]>) -> Self {
338        let margin = Vec3::from(margin.into());
339        let min = self.min - margin;
340        let max = self.max + margin;
341        Self::new_unchecked(min, max)
342    }
343
344    ///TODO: maybe we have more more efficient method (issue #1243)
345    #[inline]
346    #[must_use]
347    pub fn transform(&self, transform: impl Into<Isometry3>) -> Self {
348        let transform = transform.into();
349        let corners = self.corners();
350        let c0 = transform * corners[0];
351        let c1 = transform * corners[1];
352        let mut aabb = Aabb3::new(c0, c1);
353        for corner in corners.iter().skip(2) {
354            aabb.grow(transform * *corner);
355        }
356        aabb
357    }
358
359    /// Get the center point of the AABB
360    #[inline]
361    #[must_use]
362    pub fn center(&self) -> Point3 {
363        const HALF: Real = 0.5;
364        Point3::from((self.min.coords + self.max.coords) * HALF)
365    }
366
367    /// Get the size of the AABB (Excludes cases where the end points are equal)
368    #[inline]
369    #[must_use]
370    pub fn size(&self) -> Vec3 {
371        self.max - self.min
372    }
373
374    #[inline]
375    #[must_use]
376    pub fn get_extents(&self) -> Vec3 {
377        const HALF: Real = 0.5;
378        self.size() * HALF
379    }
380
381    /// Overlap test two AABB.
382    #[inline]
383    #[must_use]
384    pub fn overlap_test(&self, rhs: &Aabb3) -> bool {
385        Self::overlap_test_1d(self.min[0], self.max[0], rhs.min[0], rhs.max[0])
386            && Self::overlap_test_1d(self.min[1], self.max[1], rhs.min[1], rhs.max[1])
387            && Self::overlap_test_1d(self.min[2], self.max[2], rhs.min[2], rhs.max[2])
388    }
389
390    #[inline]
391    fn overlap_test_1d(min0: Real, max0: Real, min1: Real, max1: Real) -> bool {
392        max0 > min1 && min0 < max1
393    }
394
395    #[inline]
396    #[must_use]
397    pub fn raycast(&self, ray: Ray, max_distance: Real) -> bool {
398        self.raycast_by_one_over_direction(ray.origin, ray.one_over_direction(), max_distance)
399    }
400
401    /// # Examples
402    /// ```
403    /// use phys_geom::{Aabb3, Ray};
404    /// use phys_geom::{math::Point3, math::Vec3};
405    /// let ray = Ray::new_with_vec3(Point3::origin(), Vec3::new(0.0, 0.0, 1.0));
406    /// let aabb = Aabb3::new(Point3::origin(), Point3::new(1.0, 1.0, 1.0));
407    /// aabb.raycast_by_one_over_direction(ray.origin, ray.one_over_direction(), 1.0);
408    /// ```
409    #[inline]
410    #[must_use]
411    pub fn raycast_by_one_over_direction(
412        &self,
413        origin: impl Into<[Real; 3]>,
414        one_over_direction: impl Into<[Real; 3]>,
415        max_distance: Real,
416    ) -> bool {
417        self.raycast_by_one_over_direction_impl(origin, one_over_direction, max_distance)
418            .is_some()
419    }
420
421    /// Raycast the AABB with one over direction.
422    /// Return:
423    ///  * distance vector to each entry side of the AABB,
424    ///  * entry distance
425    #[inline]
426    pub fn raycast_by_one_over_direction_impl(
427        &self,
428        origin: impl Into<[Real; 3]>,
429        one_over_direction: impl Into<[Real; 3]>,
430        max_distance: Real,
431    ) -> Option<(Vec3, Real)> {
432        let origin = Point3::from(origin.into());
433        let one_over_direction = Vec3::from(one_over_direction.into());
434        let t0 = (self.min - origin).component_mul(&one_over_direction);
435        let t1 = (self.max - origin).component_mul(&one_over_direction);
436        let term_exit = t0.sup(&t1);
437        let term_entry = t0.inf(&t1);
438        let exit = Real::min(max_distance, term_exit.min());
439        let max_entry = term_entry.max();
440        let term = Real::max(0.0, max_entry);
441        if term <= exit {
442            Some((term_entry, max_entry))
443        } else {
444            None
445        }
446    }
447
448    #[inline]
449    #[must_use]
450    pub fn xz(&self) -> Aabb2 {
451        Aabb2::new(self.min.xz(), self.max.xz())
452    }
453
454    #[inline]
455    #[must_use]
456    pub fn xy(&self) -> Aabb2 {
457        Aabb2::new(self.min.xy(), self.max.xy())
458    }
459
460    #[inline]
461    #[must_use]
462    pub fn yz(&self) -> Aabb2 {
463        Aabb2::new(self.min.yz(), self.max.yz())
464    }
465}
466
467impl Add<Vec3> for Aabb3 {
468    type Output = Aabb3;
469
470    #[inline]
471    fn add(self, rhs: Vec3) -> Self::Output {
472        let mut aabb = self;
473        aabb.offset(rhs);
474        aabb
475    }
476}
477
478impl Sub<Vec3> for Aabb3 {
479    type Output = Aabb3;
480
481    #[inline]
482    fn sub(self, rhs: Vec3) -> Self::Output {
483        let min = self.min - rhs;
484        let max = self.max - rhs;
485        Aabb3::new_unchecked(min, max)
486    }
487}
488
489impl AddAssign<Vec3> for Aabb3 {
490    #[inline]
491    fn add_assign(&mut self, rhs: Vec3) {
492        self.min += rhs;
493        self.max += rhs;
494    }
495}
496
497impl SubAssign<Vec3> for Aabb3 {
498    #[inline]
499    fn sub_assign(&mut self, rhs: Vec3) {
500        self.min -= rhs;
501        self.max -= rhs;
502    }
503}
504
505pub trait ComputeAabb3 {
506    /// Compute the AABB in local space.
507    fn compute_aabb(&self) -> Aabb3;
508}
509
510impl ComputeAabb3 for Sphere {
511    #[inline]
512    fn compute_aabb(&self) -> Aabb3 {
513        let r = self.radius();
514        Aabb3::new_unchecked(Point3::new(-r, -r, -r), Point3::new(r, r, r))
515    }
516}
517
518impl ComputeAabb3 for Cuboid {
519    #[inline]
520    fn compute_aabb(&self) -> Aabb3 {
521        let [hx, hy, hz] = self.half_length();
522        Aabb3 {
523            min: Point3::new(-hx, -hy, -hz),
524            max: Point3::new(hx, hy, hz),
525        }
526    }
527}
528
529impl ComputeAabb3 for Capsule {
530    #[inline]
531    fn compute_aabb(&self) -> Aabb3 {
532        let r = self.radius();
533        let hh = self.half_height();
534        Aabb3::new_unchecked(Point3::new(-r, -hh - r, -r), Point3::new(r, hh + r, r))
535    }
536}
537
538impl ComputeAabb3 for Cylinder {
539    #[inline]
540    fn compute_aabb(&self) -> Aabb3 {
541        let radius = self.radius();
542        let half_height = self.half_height();
543        Aabb3::new_unchecked(
544            Point3::new(-radius, -half_height, -radius),
545            Point3::new(radius, half_height, radius),
546        )
547    }
548}
549
550// infinite plane's aabb usually covers the entire world,
551// since the plane is always axis-aligned in its space, we can compute a smaller aabb
552impl ComputeAabb3 for InfinitePlane {
553    fn compute_aabb(&self) -> Aabb3 {
554        // limit the aabb to a reasonable large enough size
555        let max_aabb = Real::MAX * 0.25;
556        let min_aabb = Real::MIN * 0.25;
557        Aabb3::new_unchecked(
558            Point3::new(min_aabb, min_aabb, min_aabb),
559            Point3::new(max_aabb, 0.0, max_aabb),
560        )
561    }
562}
563
564#[cfg(test)]
565mod tests {
566    use approx::assert_relative_eq;
567
568    use super::*;
569    use crate::math::UnitVec3;
570    use crate::Ray;
571
572    #[test]
573    fn test_aabb_grow() {
574        let mut aabb = Aabb3::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
575        aabb.grow(Point3::new(-1.0, 2.0, 3.0));
576        assert_relative_eq!(aabb.min(), Point3::new(-1.0, 0.0, 0.0));
577        assert_relative_eq!(aabb.max(), Point3::new(1.0, 2.0, 3.0));
578    }
579
580    #[test]
581    fn test_aabb_corners() {
582        let aabb = Aabb3::new(Point3::new(-1.0, -2.0, -3.0), Point3::new(1.0, 2.0, 3.0));
583        let corners = aabb.corners();
584        assert_relative_eq!(corners[0], Point3::new(-1.0, -2.0, -3.0));
585        assert_relative_eq!(corners[1], Point3::new(-1.0, -2.0, 3.0));
586        assert_relative_eq!(corners[2], Point3::new(-1.0, 2.0, -3.0));
587        assert_relative_eq!(corners[3], Point3::new(-1.0, 2.0, 3.0));
588
589        assert_relative_eq!(corners[4], Point3::new(1.0, -2.0, -3.0));
590        assert_relative_eq!(corners[5], Point3::new(1.0, -2.0, 3.0));
591        assert_relative_eq!(corners[6], Point3::new(1.0, 2.0, -3.0));
592        assert_relative_eq!(corners[7], Point3::new(1.0, 2.0, 3.0));
593    }
594
595    #[test]
596    fn test_raycast() {
597        let aabb = Aabb3::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
598
599        let ray_origin_0 = Point3::new(0.5, 0.5, -0.5);
600        let ray_direction_0 = UnitVec3::new_normalize(Vec3::new(0.0, 0.0, 1.0));
601        let max_distance_0 = 1.0;
602        assert!(aabb.raycast_by_one_over_direction(
603            ray_origin_0,
604            Ray::compute_one_over_direction(ray_direction_0),
605            max_distance_0
606        ));
607
608        {
609            let ray_origin_1 = Point3::new(0.5, 1.1, -0.5);
610            assert!(!aabb.raycast_by_one_over_direction(
611                ray_origin_1,
612                Ray::compute_one_over_direction(ray_direction_0),
613                max_distance_0
614            ));
615        }
616
617        {
618            let ray_direction_2 = UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0));
619            assert!(!aabb.raycast_by_one_over_direction(
620                ray_origin_0,
621                Ray::compute_one_over_direction(ray_direction_2),
622                max_distance_0
623            ));
624        }
625
626        {
627            let max_distance_3 = 0.3;
628            assert!(!aabb.raycast_by_one_over_direction(
629                ray_origin_0,
630                Ray::compute_one_over_direction(ray_direction_0),
631                max_distance_3
632            ));
633        }
634
635        {
636            let ray_direction_4 = UnitVec3::new_normalize(Vec3::new(0.0, 0.4, 0.5));
637            assert!(aabb.raycast_by_one_over_direction(
638                ray_origin_0,
639                Ray::compute_one_over_direction(ray_direction_4),
640                max_distance_0
641            ));
642        }
643
644        // The origin is inside the AABB.
645        {
646            let ray_origin_5 = Point3::new(0.5, 0.5, 0.5);
647            assert!(aabb.raycast_by_one_over_direction(
648                ray_origin_5,
649                Ray::compute_one_over_direction(ray_direction_0),
650                max_distance_0
651            ));
652        }
653    }
654}