Skip to content

Commit fdc01bf

Browse files
authored
perf: raycasting geometry +50% (#825)
^ --------- Co-authored-by: Shidowy <[email protected]>
1 parent 2fa8137 commit fdc01bf

File tree

1 file changed

+50
-94
lines changed

1 file changed

+50
-94
lines changed

crates/geometry/src/aabb.rs

Lines changed: 50 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,13 @@ impl From<[f32; 6]> for Aabb {
6969

7070
impl FromIterator<Self> for Aabb {
7171
fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
72-
let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
73-
let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
74-
75-
for aabb in iter {
76-
min = min.min(aabb.min);
77-
max = max.max(aabb.max);
78-
}
79-
72+
let infinity = Vec3::splat(f32::INFINITY);
73+
let neg_infinity = Vec3::splat(f32::NEG_INFINITY);
74+
let (min, max) = iter
75+
.into_iter()
76+
.fold((infinity, neg_infinity), |(min, max), aabb| {
77+
(min.min(aabb.min), max.max(aabb.max))
78+
});
8079
Self { min, max }
8180
}
8281
}
@@ -183,6 +182,7 @@ impl Aabb {
183182
Self { min, max }
184183
}
185184

185+
#[inline]
186186
#[must_use]
187187
pub fn move_by(&self, offset: Vec3) -> Self {
188188
Self {
@@ -193,56 +193,39 @@ impl Aabb {
193193

194194
#[must_use]
195195
pub fn overlap(a: &Self, b: &Self) -> Option<Self> {
196-
let min_x = a.min.x.max(b.min.x);
197-
let min_y = a.min.y.max(b.min.y);
198-
let min_z = a.min.z.max(b.min.z);
199-
200-
let max_x = a.max.x.min(b.max.x);
201-
let max_y = a.max.y.min(b.max.y);
202-
let max_z = a.max.z.min(b.max.z);
203-
204-
// Check if there is an overlap. If any dimension does not overlap, return None.
205-
if min_x < max_x && min_y < max_y && min_z < max_z {
206-
Some(Self {
207-
min: Vec3::new(min_x, min_y, min_z),
208-
max: Vec3::new(max_x, max_y, max_z),
209-
})
196+
let min = a.min.max(b.min);
197+
let max = a.max.min(b.max);
198+
if min.cmplt(max).all() {
199+
Some(Self { min, max })
210200
} else {
211201
None
212202
}
213203
}
214204

205+
#[inline]
215206
#[must_use]
216207
pub fn collides(&self, other: &Self) -> bool {
217-
self.min.x <= other.max.x
218-
&& self.max.x >= other.min.x
219-
&& self.min.y <= other.max.y
220-
&& self.max.y >= other.min.y
221-
&& self.min.z <= other.max.z
222-
&& self.max.z >= other.min.z
208+
(self.min.cmple(other.max) & self.max.cmpge(other.min)).all()
223209
}
224210

211+
#[inline]
225212
#[must_use]
226213
pub fn collides_point(&self, point: Vec3) -> bool {
227-
self.min.x <= point.x
228-
&& point.x <= self.max.x
229-
&& self.min.y <= point.y
230-
&& point.y <= self.max.y
231-
&& self.min.z <= point.z
232-
&& point.z <= self.max.z
214+
(self.min.cmple(point) & point.cmple(self.max)).all()
233215
}
234216

235217
#[must_use]
236-
pub fn dist2(&self, point: Vec3) -> f64 {
237-
let point = point.as_dvec3();
238-
// Clamp the point into the box volume.
239-
let clamped = point.clamp(self.min.as_dvec3(), self.max.as_dvec3());
240-
241-
// Distance vector from point to the clamped point inside the box.
242-
let diff = point - clamped;
218+
pub fn batch_collides(&self, others: &[Self]) -> Vec<bool> {
219+
others.iter().map(|other| self.collides(other)).collect()
220+
}
243221

244-
// The squared distance.
245-
diff.length_squared()
222+
#[must_use]
223+
pub fn dist2(&self, point: Vec3) -> f64 {
224+
let point_d = point.as_dvec3();
225+
let min_d = self.min.as_dvec3();
226+
let max_d = self.max.as_dvec3();
227+
let clamped = point_d.clamp(min_d, max_d);
228+
(point_d - clamped).length_squared()
246229
}
247230

248231
pub fn overlaps<'a, T>(
@@ -258,9 +241,10 @@ impl Aabb {
258241
#[must_use]
259242
pub fn surface_area(&self) -> f32 {
260243
let lens = self.lens();
261-
2.0 * lens
262-
.z
263-
.mul_add(lens.x, lens.x.mul_add(lens.y, lens.y * lens.z))
244+
let xy = lens.x * lens.y;
245+
let yz = lens.y * lens.z;
246+
let xz = lens.x * lens.z;
247+
2.0 * (xy + yz + xz)
264248
}
265249

266250
#[must_use]
@@ -269,63 +253,34 @@ impl Aabb {
269253
lens.x * lens.y * lens.z
270254
}
271255

256+
#[inline]
272257
#[must_use]
273258
pub fn intersect_ray(&self, ray: &Ray) -> Option<NotNan<f32>> {
274259
let origin = ray.origin();
275-
276-
// If the ray is originating inside the AABB, we can immediately return.
277-
if self.contains_point(origin) {
278-
return Some(NotNan::new(0.0).unwrap());
279-
}
280-
281260
let dir = ray.direction();
282261
let inv_dir = ray.inv_direction();
283262

284-
// Initialize t_min and t_max to the range of possible values
285-
let (mut t_min, mut t_max) = (f32::NEG_INFINITY, f32::INFINITY);
286-
287-
// X-axis
288-
if dir.x != 0.0 {
289-
let tx1 = (self.min.x - origin.x) * inv_dir.x;
290-
let tx2 = (self.max.x - origin.x) * inv_dir.x;
291-
t_min = t_min.max(tx1.min(tx2));
292-
t_max = t_max.min(tx1.max(tx2));
293-
} else if origin.x < self.min.x || origin.x > self.max.x {
294-
return None; // Ray is parallel to X slab and outside the slab
295-
}
296-
297-
// Y-axis
298-
if dir.y != 0.0 {
299-
let ty1 = (self.min.y - origin.y) * inv_dir.y;
300-
let ty2 = (self.max.y - origin.y) * inv_dir.y;
301-
t_min = t_min.max(ty1.min(ty2));
302-
t_max = t_max.min(ty1.max(ty2));
303-
} else if origin.y < self.min.y || origin.y > self.max.y {
304-
return None; // Ray is parallel to Y slab and outside the slab
305-
}
306-
307-
// Z-axis
308-
if dir.z != 0.0 {
309-
let tz1 = (self.min.z - origin.z) * inv_dir.z;
310-
let tz2 = (self.max.z - origin.z) * inv_dir.z;
311-
t_min = t_min.max(tz1.min(tz2));
312-
t_max = t_max.min(tz1.max(tz2));
313-
} else if origin.z < self.min.z || origin.z > self.max.z {
314-
return None; // Ray is parallel to Z slab and outside the slab
263+
let mut t1 = (self.min - origin) * inv_dir;
264+
let mut t2 = (self.max - origin) * inv_dir;
265+
266+
for axis in 0..3 {
267+
if dir[axis] == 0.0 {
268+
if !(self.min[axis] <= origin[axis] && origin[axis] <= self.max[axis]) {
269+
return None;
270+
}
271+
t1[axis] = -f32::INFINITY;
272+
t2[axis] = f32::INFINITY;
273+
}
315274
}
316275

317-
if t_min > t_max {
318-
return None;
319-
}
276+
let t_min = t1.min(t2).max(Vec3::splat(0.0));
277+
let t_max = t1.max(t2);
320278

321-
// At this point, t_min and t_max define the intersection range.
322-
// If t_min < 0.0, it means we start “behind” the origin; if t_max < 0.0, no intersection in front.
323-
let t_hit = if t_min >= 0.0 { t_min } else { t_max };
324-
if t_hit < 0.0 {
325-
return None;
279+
if t_min.max_element() <= t_max.min_element() {
280+
Some(NotNan::new(t_min.max_element()).unwrap())
281+
} else {
282+
None
326283
}
327-
328-
Some(NotNan::new(t_hit).unwrap())
329284
}
330285

331286
#[must_use]
@@ -366,6 +321,7 @@ impl Aabb {
366321
(self.min.z + self.max.z) / 2.0
367322
}
368323

324+
#[inline]
369325
#[must_use]
370326
pub fn lens(&self) -> Vec3 {
371327
self.max - self.min
@@ -538,7 +494,7 @@ mod tests {
538494
#[test]
539495
fn test_ray_touching_aabb_boundary() {
540496
let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
541-
// Ray parallel to one axis and just touches at x = -1
497+
542498
let ray = Ray::new(Vec3::new(-2.0, 1.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
543499
let intersection = aabb.intersect_ray(&ray);
544500
assert!(

0 commit comments

Comments
 (0)