Skip to main content

box3d_rust/
aabb.rs

1// Port of box3d-cpp-reference/src/aabb.h and src/aabb.c
2//
3// `b3IsValidAABB` / AABB query helpers (contains/center/extents/union/overlaps/area)
4// live in math_functions. This module holds the pieces specific to aabb.c/aabb.h:
5// surface-area perimeter, in-place enlarge, farthest corner, and ray cast.
6//
7// SPDX-FileCopyrightText: 2025 Erin Catto
8// SPDX-License-Identifier: MIT
9
10use crate::math_functions::{
11    abs_float, clamp_float, length, max_float, min_float, mul_sv, sub, Aabb, Vec3,
12};
13
14/// Get the surface area of an AABB. (b3Perimeter)
15///
16/// Named "perimeter" after the 2D Box2D heritage; in 3D this is the box surface area.
17/// Operation order matches aabb.h (not `aabb_area`, which uses a different multiply order).
18pub fn perimeter(a: Aabb) -> f32 {
19    let wx = a.upper_bound.x - a.lower_bound.x;
20    let wy = a.upper_bound.y - a.lower_bound.y;
21    let wz = a.upper_bound.z - a.lower_bound.z;
22    2.0 * (wx * wz + wy * wx + wz * wy)
23}
24
25/// Enlarge `a` to contain `b`. (b3EnlargeAABB)
26///
27/// @return true if the AABB grew.
28pub fn enlarge_aabb(a: &mut Aabb, b: Aabb) -> bool {
29    let mut changed = false;
30    if b.lower_bound.x < a.lower_bound.x {
31        a.lower_bound.x = b.lower_bound.x;
32        changed = true;
33    }
34
35    if b.lower_bound.y < a.lower_bound.y {
36        a.lower_bound.y = b.lower_bound.y;
37        changed = true;
38    }
39
40    if b.lower_bound.z < a.lower_bound.z {
41        a.lower_bound.z = b.lower_bound.z;
42        changed = true;
43    }
44
45    if a.upper_bound.x < b.upper_bound.x {
46        a.upper_bound.x = b.upper_bound.x;
47        changed = true;
48    }
49
50    if a.upper_bound.y < b.upper_bound.y {
51        a.upper_bound.y = b.upper_bound.y;
52        changed = true;
53    }
54
55    if a.upper_bound.z < b.upper_bound.z {
56        a.upper_bound.z = b.upper_bound.z;
57        changed = true;
58    }
59
60    changed
61}
62
63/// Return the AABB corner farthest from `p` along each axis independently.
64/// (b3FarthestPointOnAABB)
65pub fn farthest_point_on_aabb(b: Aabb, p: Vec3) -> Vec3 {
66    Vec3 {
67        x: if (p.x - b.lower_bound.x) > (b.upper_bound.x - p.x) {
68            b.lower_bound.x
69        } else {
70            b.upper_bound.x
71        },
72        y: if (p.y - b.lower_bound.y) > (b.upper_bound.y - p.y) {
73            b.lower_bound.y
74        } else {
75            b.upper_bound.y
76        },
77        z: if (p.z - b.lower_bound.z) > (b.upper_bound.z - p.z) {
78            b.lower_bound.z
79        } else {
80            b.upper_bound.z
81        },
82    }
83}
84
85/// Clip the ray interval against one AABB slab axis.
86///
87/// Returns `false` if the ray misses this slab.
88fn clip_slab(
89    ray_component: f32,
90    ray_start: f32,
91    box_min: f32,
92    box_max: f32,
93    t_min: &mut f32,
94    t_max: &mut f32,
95) -> bool {
96    if abs_float(ray_component) < f32::EPSILON {
97        // Ray is parallel to slab, check if ray origin is within slab
98        if ray_start < box_min || ray_start > box_max {
99            return false;
100        }
101    } else {
102        // Compute intersection distances
103        let mut t1 = (box_min - ray_start) / ray_component;
104        let mut t2 = (box_max - ray_start) / ray_component;
105
106        // Ensure t1 <= t2
107        if t1 > t2 {
108            core::mem::swap(&mut t1, &mut t2);
109        }
110
111        // Update intersection interval
112        *t_min = max_float(*t_min, t1);
113        *t_max = min_float(*t_max, t2);
114
115        // Check for no intersection
116        if *t_min > *t_max {
117            return false;
118        }
119    }
120    true
121}
122
123/// Ray cast an AABB. This is a custom function used by height fields.
124/// (b3RayCastAABB)
125///
126/// Similar to Real-time Collision Detection, p179.
127/// On hit, writes the entry/exit fractions along the segment into
128/// `min_fraction` / `max_fraction` (clamped to \[0, 1\]).
129pub fn ray_cast_aabb(
130    a: Aabb,
131    p1: Vec3,
132    p2: Vec3,
133    min_fraction: &mut f32,
134    max_fraction: &mut f32,
135) -> bool {
136    // Ray direction and length
137    let d = sub(p2, p1);
138    let ray_length = length(d);
139
140    // Handle degenerate ray
141    if ray_length < f32::EPSILON {
142        // Check if point is inside AABB
143        if p1.x >= a.lower_bound.x
144            && p1.x <= a.upper_bound.x
145            && p1.y >= a.lower_bound.y
146            && p1.y <= a.upper_bound.y
147            && p1.z >= a.lower_bound.z
148            && p1.z <= a.upper_bound.z
149        {
150            *min_fraction = 0.0;
151            *max_fraction = 0.0;
152            return true;
153        }
154
155        return false;
156    }
157
158    let ray_dir = mul_sv(1.0 / ray_length, d);
159
160    // Slab method for ray-AABB intersection
161    let mut t_min = 0.0;
162    let mut t_max = ray_length;
163
164    if !clip_slab(
165        ray_dir.x,
166        p1.x,
167        a.lower_bound.x,
168        a.upper_bound.x,
169        &mut t_min,
170        &mut t_max,
171    ) {
172        return false;
173    }
174
175    if !clip_slab(
176        ray_dir.y,
177        p1.y,
178        a.lower_bound.y,
179        a.upper_bound.y,
180        &mut t_min,
181        &mut t_max,
182    ) {
183        return false;
184    }
185
186    if !clip_slab(
187        ray_dir.z,
188        p1.z,
189        a.lower_bound.z,
190        a.upper_bound.z,
191        &mut t_min,
192        &mut t_max,
193    ) {
194        return false;
195    }
196
197    // Check if intersection is behind ray start
198    if t_max < 0.0 {
199        return false;
200    }
201
202    // Convert distances to fractions
203    *min_fraction = clamp_float(t_min / ray_length, 0.0, 1.0);
204    *max_fraction = clamp_float(t_max / ray_length, 0.0, 1.0);
205
206    true
207}