1use core::f32::consts::FRAC_PI_2;
2
3use glam::{Vec2, Vec3A, Vec3Swizzles};
4
5use crate::{
6 bounding::{BoundingCircle, BoundingVolume},
7 ops,
8 primitives::{
9 BoxedPolygon, BoxedPolyline2d, Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d,
10 Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d,
11 },
12 Isometry2d, Isometry3d, Quat, Rot2,
13};
14
15use crate::{bounding::Bounded2d, primitives::Circle};
16
17use super::{Aabb3d, Bounded3d, BoundingSphere};
18
19impl BoundedExtrusion for Circle {
20 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
21 let isometry = isometry.into();
24
25 let segment_dir = isometry.rotation * Vec3A::Z;
26 let top = (segment_dir * half_depth).abs();
27
28 let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
29 let half_size = self.radius * Vec3A::new(e.x.sqrt(), e.y.sqrt(), e.z.sqrt());
30
31 Aabb3d {
32 min: isometry.translation - half_size - top,
33 max: isometry.translation + half_size + top,
34 }
35 }
36}
37
38impl BoundedExtrusion for Ellipse {
39 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
40 let isometry = isometry.into();
41 let Vec2 { x: a, y: b } = self.half_size;
42 let normal = isometry.rotation * Vec3A::Z;
43 let conjugate_rot = isometry.rotation.conjugate();
44
45 let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {
46 let Some(axis) = (conjugate_rot * axis.reject_from(normal))
47 .xy()
48 .try_normalize()
49 else {
50 return Vec3A::ZERO;
51 };
52
53 if axis.element_product() == 0. {
54 return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);
55 }
56 let m = -axis.x / axis.y;
57 let signum = axis.signum();
58
59 let y = signum.y * b * b / (b * b + m * m * a * a).sqrt();
60 let x = signum.x * a * (1. - y * y / b / b).sqrt();
61 isometry.rotation * Vec3A::new(x, y, 0.)
62 });
63
64 let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
65 Aabb3d::new(isometry.translation, half_size)
66 }
67}
68
69impl BoundedExtrusion for Line2d {
70 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
71 let isometry = isometry.into();
72 let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
73 let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
74
75 let max = f32::MAX / 2.;
76 let half_size = Vec3A::new(
77 if dir.x == 0. { half_depth.x } else { max },
78 if dir.y == 0. { half_depth.y } else { max },
79 if dir.z == 0. { half_depth.z } else { max },
80 );
81
82 Aabb3d::new(isometry.translation, half_size)
83 }
84}
85
86impl BoundedExtrusion for Segment2d {
87 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
88 let isometry = isometry.into();
89 let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
90 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
91
92 Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())
93 }
94}
95
96impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
97 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
98 let isometry = isometry.into();
99 let aabb =
100 Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
101 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
102
103 aabb.grow(depth.abs())
104 }
105}
106
107impl BoundedExtrusion for BoxedPolyline2d {
108 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
109 let isometry = isometry.into();
110 let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
111 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
112
113 aabb.grow(depth.abs())
114 }
115}
116
117impl BoundedExtrusion for Triangle2d {
118 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
119 let isometry = isometry.into();
120 let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
121 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
122
123 aabb.grow(depth.abs())
124 }
125}
126
127impl BoundedExtrusion for Rectangle {
128 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
129 Cuboid {
130 half_size: self.half_size.extend(half_depth),
131 }
132 .aabb_3d(isometry)
133 }
134}
135
136impl<const N: usize> BoundedExtrusion for Polygon<N> {
137 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
138 let isometry = isometry.into();
139 let aabb =
140 Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
141 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
142
143 aabb.grow(depth.abs())
144 }
145}
146
147impl BoundedExtrusion for BoxedPolygon {
148 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
149 let isometry = isometry.into();
150 let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
151 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
152
153 aabb.grow(depth.abs())
154 }
155}
156
157impl BoundedExtrusion for RegularPolygon {
158 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
159 let isometry = isometry.into();
160 let aabb = Aabb3d::from_point_cloud(
161 isometry,
162 self.vertices(0.).into_iter().map(|v| v.extend(0.)),
163 );
164 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
165
166 aabb.grow(depth.abs())
167 }
168}
169
170impl BoundedExtrusion for Capsule2d {
171 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
172 let isometry = isometry.into();
173 let aabb = Cylinder {
174 half_height: half_depth,
175 radius: self.radius,
176 }
177 .aabb_3d(isometry.rotation * Quat::from_rotation_x(FRAC_PI_2));
178
179 let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
180 let half_size = aabb.max + up.abs();
181 Aabb3d::new(isometry.translation, half_size)
182 }
183}
184
185impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
186 fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
187 self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
188 }
189
190 fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
191 self.base_shape
192 .extrusion_bounding_sphere(self.half_depth, isometry)
193 }
194}
195
196pub trait BoundedExtrusion: Primitive2d + Bounded2d {
203 fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
205 let isometry = isometry.into();
206 let cap_normal = isometry.rotation * Vec3A::Z;
207 let conjugate_rot = isometry.rotation.conjugate();
208
209 let axis_values = Vec3A::AXES.map(|ax| {
211 let intersect_line = ax.cross(cap_normal);
213 if intersect_line.length_squared() <= f32::EPSILON {
214 return (0., 0.);
215 };
216
217 let line_normal = (conjugate_rot * intersect_line).yx();
219 let angle = line_normal.to_angle();
220
221 let scale = cap_normal.reject_from(ax).length();
224
225 let aabb2d = self.aabb_2d(Rot2::radians(angle));
228 (aabb2d.half_size().x * scale, aabb2d.center().x * scale)
229 });
230
231 let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
232 let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
233 let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
234
235 Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())
236 }
237
238 fn extrusion_bounding_sphere(
240 &self,
241 half_depth: f32,
242 isometry: impl Into<Isometry3d>,
243 ) -> BoundingSphere {
244 let isometry = isometry.into();
245
246 let BoundingCircle {
251 center,
252 circle: Circle { radius },
253 } = self.bounding_circle(Isometry2d::IDENTITY);
254 let radius = ops::hypot(radius, half_depth);
255 let center = isometry * Vec3A::from(center.extend(0.));
256
257 BoundingSphere::new(center, radius)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use core::f32::consts::FRAC_PI_4;
264
265 use glam::{EulerRot, Quat, Vec2, Vec3, Vec3A};
266
267 use crate::{
268 bounding::{Bounded3d, BoundingVolume},
269 ops,
270 primitives::{
271 Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
272 RegularPolygon, Segment2d, Triangle2d,
273 },
274 Dir2, Isometry3d,
275 };
276
277 #[test]
278 fn circle() {
279 let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
280 let translation = Vec3::new(2.0, 1.0, 0.0);
281
282 let aabb = cylinder.aabb_3d(translation);
283 assert_eq!(aabb.center(), Vec3A::from(translation));
284 assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
285
286 let bounding_sphere = cylinder.bounding_sphere(translation);
287 assert_eq!(bounding_sphere.center, translation.into());
288 assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
289 }
290
291 #[test]
292 fn ellipse() {
293 let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
294 let translation = Vec3::new(3., 4., 5.);
295 let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);
296 let isometry = Isometry3d::new(translation, rotation);
297
298 let aabb = extrusion.aabb_3d(isometry);
299 assert_eq!(aabb.center(), Vec3A::from(translation));
300 assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));
301
302 let bounding_sphere = extrusion.bounding_sphere(isometry);
303 assert_eq!(bounding_sphere.center, translation.into());
304 assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
305 }
306
307 #[test]
308 fn line() {
309 let extrusion = Extrusion::new(
310 Line2d {
311 direction: Dir2::new_unchecked(Vec2::Y),
312 },
313 4.,
314 );
315 let translation = Vec3::new(3., 4., 5.);
316 let rotation = Quat::from_rotation_y(FRAC_PI_4);
317 let isometry = Isometry3d::new(translation, rotation);
318
319 let aabb = extrusion.aabb_3d(isometry);
320 assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
321 assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));
322
323 let bounding_sphere = extrusion.bounding_sphere(isometry);
324 assert_eq!(bounding_sphere.center(), translation.into());
325 assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);
326 }
327
328 #[test]
329 fn rectangle() {
330 let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
331 let translation = Vec3::new(3., 4., 5.);
332 let rotation = Quat::from_rotation_z(FRAC_PI_4);
333 let isometry = Isometry3d::new(translation, rotation);
334
335 let aabb = extrusion.aabb_3d(isometry);
336 assert_eq!(aabb.center(), translation.into());
337 assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));
338
339 let bounding_sphere = extrusion.bounding_sphere(isometry);
340 assert_eq!(bounding_sphere.center, translation.into());
341 assert_eq!(bounding_sphere.radius(), 2.291288);
342 }
343
344 #[test]
345 fn segment() {
346 let extrusion = Extrusion::new(Segment2d::new(Dir2::new_unchecked(Vec2::NEG_Y), 3.), 4.0);
347 let translation = Vec3::new(3., 4., 5.);
348 let rotation = Quat::from_rotation_x(FRAC_PI_4);
349 let isometry = Isometry3d::new(translation, rotation);
350
351 let aabb = extrusion.aabb_3d(isometry);
352 assert_eq!(aabb.center(), translation.into());
353 assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));
354
355 let bounding_sphere = extrusion.bounding_sphere(isometry);
356 assert_eq!(bounding_sphere.center, translation.into());
357 assert_eq!(bounding_sphere.radius(), 2.5);
358 }
359
360 #[test]
361 fn polyline() {
362 let polyline = Polyline2d::<4>::new([
363 Vec2::ONE,
364 Vec2::new(-1.0, 1.0),
365 Vec2::NEG_ONE,
366 Vec2::new(1.0, -1.0),
367 ]);
368 let extrusion = Extrusion::new(polyline, 3.0);
369 let translation = Vec3::new(3., 4., 5.);
370 let rotation = Quat::from_rotation_x(FRAC_PI_4);
371 let isometry = Isometry3d::new(translation, rotation);
372
373 let aabb = extrusion.aabb_3d(isometry);
374 assert_eq!(aabb.center(), translation.into());
375 assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
376
377 let bounding_sphere = extrusion.bounding_sphere(isometry);
378 assert_eq!(bounding_sphere.center, translation.into());
379 assert_eq!(bounding_sphere.radius(), 2.0615528);
380 }
381
382 #[test]
383 fn triangle() {
384 let triangle = Triangle2d::new(
385 Vec2::new(0.0, 1.0),
386 Vec2::new(-10.0, -1.0),
387 Vec2::new(10.0, -1.0),
388 );
389 let extrusion = Extrusion::new(triangle, 3.0);
390 let translation = Vec3::new(3., 4., 5.);
391 let rotation = Quat::from_rotation_x(FRAC_PI_4);
392 let isometry = Isometry3d::new(translation, rotation);
393
394 let aabb = extrusion.aabb_3d(isometry);
395 assert_eq!(aabb.center(), translation.into());
396 assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));
397
398 let bounding_sphere = extrusion.bounding_sphere(isometry);
399 assert_eq!(
400 bounding_sphere.center,
401 Vec3A::new(3.0, 3.2928934, 4.2928934)
402 );
403 assert_eq!(bounding_sphere.radius(), 10.111875);
404 }
405
406 #[test]
407 fn polygon() {
408 let polygon = Polygon::<4>::new([
409 Vec2::ONE,
410 Vec2::new(-1.0, 1.0),
411 Vec2::NEG_ONE,
412 Vec2::new(1.0, -1.0),
413 ]);
414 let extrusion = Extrusion::new(polygon, 3.0);
415 let translation = Vec3::new(3., 4., 5.);
416 let rotation = Quat::from_rotation_x(FRAC_PI_4);
417 let isometry = Isometry3d::new(translation, rotation);
418
419 let aabb = extrusion.aabb_3d(isometry);
420 assert_eq!(aabb.center(), translation.into());
421 assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
422
423 let bounding_sphere = extrusion.bounding_sphere(isometry);
424 assert_eq!(bounding_sphere.center, translation.into());
425 assert_eq!(bounding_sphere.radius(), 2.0615528);
426 }
427
428 #[test]
429 fn regular_polygon() {
430 let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
431 let translation = Vec3::new(3., 4., 5.);
432 let rotation = Quat::from_rotation_x(FRAC_PI_4);
433 let isometry = Isometry3d::new(translation, rotation);
434
435 let aabb = extrusion.aabb_3d(isometry);
436 assert_eq!(
437 aabb.center(),
438 Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
439 );
440 assert_eq!(
441 aabb.half_size(),
442 Vec3A::new(1.9498558, 2.7584014, 2.7584019)
443 );
444
445 let bounding_sphere = extrusion.bounding_sphere(isometry);
446 assert_eq!(bounding_sphere.center, translation.into());
447 assert_eq!(bounding_sphere.radius(), 8f32.sqrt());
448 }
449
450 #[test]
451 fn capsule() {
452 let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
453 let translation = Vec3::new(3., 4., 5.);
454 let rotation = Quat::from_rotation_x(FRAC_PI_4);
455 let isometry = Isometry3d::new(translation, rotation);
456
457 let aabb = extrusion.aabb_3d(isometry);
458 assert_eq!(aabb.center(), translation.into());
459 assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));
460
461 let bounding_sphere = extrusion.bounding_sphere(isometry);
462 assert_eq!(bounding_sphere.center, translation.into());
463 assert_eq!(bounding_sphere.radius(), 2.5);
464 }
465}