Skip to content

Commit c62ad4b

Browse files
authored
Implement bounding volumes for primitive shapes (#11336)
# Objective Closes #10570. #10946 added bounding volume types and traits, but didn't use them for anything yet. This PR implements `Bounded2d` and `Bounded3d` for Bevy's primitive shapes. ## Solution Implement `Bounded2d` and `Bounded3d` for primitive shapes. This allows computing AABBs and bounding circles/spheres for them. For most shapes, there are several ways of implementing bounding volumes. I took inspiration from [Parry's bounding volumes](https://github.com/dimforge/parry/tree/master/src/bounding_volume), [Inigo Quilez](http://iquilezles.org/articles/diskbbox/), and figured out the rest myself using geometry. I tried to comment all slightly non-trivial or unclear math to make it understandable. Parry uses support mapping (finding the farthest point in some direction for convex shapes) for some AABBs like cones, cylinders, and line segments. This involves several quat operations and normalizations, so I opted for the simpler and more efficient geometric approaches shown in [Quilez's article](http://iquilezles.org/articles/diskbbox/). Below you can see some of the bounding volumes working in 2D and 3D. Note that I can't conveniently add these examples yet because they use primitive shape meshing, which is still WIP. https://github.com/bevyengine/bevy/assets/57632562/4465cbc6-285b-4c71-b62d-a2b3ee16f8b4 https://github.com/bevyengine/bevy/assets/57632562/94b4ac84-a092-46d7-b438-ce2e971496a4 --- ## Changelog - Implemented `Bounded2d`/`Bounded3d` for primitive shapes - Added `from_point_cloud` method for bounding volumes (used by many bounding implementations) - Added `point_cloud_2d/3d_center` and `rotate_vec2` utility functions - Added `RegularPolygon::vertices` method (used in regular polygon AABB construction) - Added `Triangle::circumcenter` method (used in triangle bounding circle construction) - Added bounding circle/sphere creation from AABBs and vice versa ## Extra Do we want to implement `Bounded2d` for some "3D-ish" shapes too? For example, capsules are sort of dimension-agnostic and useful for 2D, so I think that would be good to implement. But a cylinder in 2D is just a rectangle, and a cone is a triangle, so they wouldn't make as much sense to me. A conical frustum would be an isosceles trapezoid, which could be useful, but I'm not sure if computing the 2D AABB of a 3D frustum makes semantic sense.
1 parent f6b40a6 commit c62ad4b

File tree

5 files changed

+1278
-1
lines changed

5 files changed

+1278
-1
lines changed

crates/bevy_math/src/bounding/bounded2d.rs renamed to crates/bevy_math/src/bounding/bounded2d/mod.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
mod primitive_impls;
2+
3+
use glam::Mat2;
4+
15
use super::BoundingVolume;
26
use crate::prelude::Vec2;
37

8+
/// Computes the geometric center of the given set of points.
9+
#[inline(always)]
10+
fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
11+
assert!(
12+
!points.is_empty(),
13+
"cannot compute the center of an empty set of points"
14+
);
15+
16+
let denom = 1.0 / points.len() as f32;
17+
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
18+
}
19+
420
/// A trait with methods that return 2D bounded volumes for a shape
521
pub trait Bounded2d {
622
/// Get an axis-aligned bounding box for the shape with the given translation and rotation.
@@ -21,6 +37,41 @@ pub struct Aabb2d {
2137
pub max: Vec2,
2238
}
2339

40+
impl Aabb2d {
41+
/// Computes the smallest [`Aabb2d`] containing the given set of points,
42+
/// transformed by `translation` and `rotation`.
43+
///
44+
/// # Panics
45+
///
46+
/// Panics if the given set of points is empty.
47+
#[inline(always)]
48+
pub fn from_point_cloud(translation: Vec2, rotation: f32, points: &[Vec2]) -> Aabb2d {
49+
// Transform all points by rotation
50+
let rotation_mat = Mat2::from_angle(rotation);
51+
let mut iter = points.iter().map(|point| rotation_mat * *point);
52+
53+
let first = iter
54+
.next()
55+
.expect("point cloud must contain at least one point for Aabb2d construction");
56+
57+
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
58+
(point.min(prev_min), point.max(prev_max))
59+
});
60+
61+
Aabb2d {
62+
min: min + translation,
63+
max: max + translation,
64+
}
65+
}
66+
67+
/// Computes the smallest [`BoundingCircle`] containing this [`Aabb2d`].
68+
#[inline(always)]
69+
pub fn bounding_circle(&self) -> BoundingCircle {
70+
let radius = self.min.distance(self.max) / 2.0;
71+
BoundingCircle::new(self.center(), radius)
72+
}
73+
}
74+
2475
impl BoundingVolume for Aabb2d {
2576
type Position = Vec2;
2677
type HalfSize = Vec2;
@@ -207,11 +258,43 @@ impl BoundingCircle {
207258
}
208259
}
209260

261+
/// Computes a [`BoundingCircle`] containing the given set of points,
262+
/// transformed by `translation` and `rotation`.
263+
///
264+
/// The bounding circle is not guaranteed to be the smallest possible.
265+
#[inline(always)]
266+
pub fn from_point_cloud(translation: Vec2, rotation: f32, points: &[Vec2]) -> BoundingCircle {
267+
let center = point_cloud_2d_center(points);
268+
let mut radius_squared = 0.0;
269+
270+
for point in points {
271+
// Get squared version to avoid unnecessary sqrt calls
272+
let distance_squared = point.distance_squared(center);
273+
if distance_squared > radius_squared {
274+
radius_squared = distance_squared;
275+
}
276+
}
277+
278+
BoundingCircle::new(
279+
Mat2::from_angle(rotation) * center + translation,
280+
radius_squared.sqrt(),
281+
)
282+
}
283+
210284
/// Get the radius of the bounding circle
211285
#[inline(always)]
212286
pub fn radius(&self) -> f32 {
213287
self.circle.radius
214288
}
289+
290+
/// Computes the smallest [`Aabb2d`] containing this [`BoundingCircle`].
291+
#[inline(always)]
292+
pub fn aabb_2d(&self) -> Aabb2d {
293+
Aabb2d {
294+
min: self.center - Vec2::splat(self.radius()),
295+
max: self.center + Vec2::splat(self.radius()),
296+
}
297+
}
215298
}
216299

217300
impl BoundingVolume for BoundingCircle {

0 commit comments

Comments
 (0)