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