1use super::functions::*;
6use crate::shape::Shape;
7use oxiphysics_core::Transform;
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Copy)]
12pub enum ChildShapeKind {
13 Sphere {
15 radius: f64,
17 },
18 Box {
20 half_extents: [f64; 3],
22 },
23 Capsule {
25 radius: f64,
27 half_height: f64,
29 },
30}
31#[derive(Debug, Clone)]
33pub struct CompoundChild {
34 pub center: [f64; 3],
36 pub shape_kind: ChildShapeKind,
38}
39#[derive(Debug, Clone)]
41pub struct CompoundAabb {
42 pub all_aabbs: Vec<([f64; 3], [f64; 3])>,
44 pub merged_min: [f64; 3],
46 pub merged_max: [f64; 3],
48}
49#[derive(Debug, Clone)]
54pub struct LocalTransform {
55 pub translation: [f64; 3],
57 pub rot: [[f64; 3]; 3],
59}
60impl LocalTransform {
61 pub fn identity() -> Self {
63 Self {
64 translation: [0.0; 3],
65 rot: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
66 }
67 }
68 pub fn from_translation(t: [f64; 3]) -> Self {
70 Self {
71 translation: t,
72 rot: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
73 }
74 }
75 pub fn local_to_world(&self, p: [f64; 3]) -> [f64; 3] {
79 let mut out = self.translation;
80 for (out_i, rot_row) in out.iter_mut().zip(self.rot.iter()) {
81 *out_i += rot_row[0] * p[0] + rot_row[1] * p[1] + rot_row[2] * p[2];
82 }
83 out
84 }
85 pub fn world_to_local(&self, p: [f64; 3]) -> [f64; 3] {
89 let d = [
90 p[0] - self.translation[0],
91 p[1] - self.translation[1],
92 p[2] - self.translation[2],
93 ];
94 [
95 self.rot[0][0] * d[0] + self.rot[1][0] * d[1] + self.rot[2][0] * d[2],
96 self.rot[0][1] * d[0] + self.rot[1][1] * d[1] + self.rot[2][1] * d[2],
97 self.rot[0][2] * d[0] + self.rot[1][2] * d[1] + self.rot[2][2] * d[2],
98 ]
99 }
100 pub fn local_to_world_dir(&self, v: [f64; 3]) -> [f64; 3] {
102 [
103 self.rot[0][0] * v[0] + self.rot[0][1] * v[1] + self.rot[0][2] * v[2],
104 self.rot[1][0] * v[0] + self.rot[1][1] * v[1] + self.rot[1][2] * v[2],
105 self.rot[2][0] * v[0] + self.rot[2][1] * v[1] + self.rot[2][2] * v[2],
106 ]
107 }
108 pub fn world_to_local_dir(&self, v: [f64; 3]) -> [f64; 3] {
110 [
111 self.rot[0][0] * v[0] + self.rot[1][0] * v[1] + self.rot[2][0] * v[2],
112 self.rot[0][1] * v[0] + self.rot[1][1] * v[1] + self.rot[2][1] * v[2],
113 self.rot[0][2] * v[0] + self.rot[1][2] * v[1] + self.rot[2][2] * v[2],
114 ]
115 }
116}
117#[derive(Debug, Clone)]
120pub struct CompoundShapeEx {
121 pub children: Vec<(LocalTransform, ChildShapeKind)>,
123}
124impl Default for CompoundShapeEx {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129impl CompoundShapeEx {
130 pub fn new() -> Self {
132 Self {
133 children: Vec::new(),
134 }
135 }
136 pub fn add_sphere(&mut self, transform: LocalTransform, radius: f64) {
138 self.children
139 .push((transform, ChildShapeKind::Sphere { radius }));
140 }
141 pub fn add_box(&mut self, transform: LocalTransform, half_extents: [f64; 3]) {
143 self.children
144 .push((transform, ChildShapeKind::Box { half_extents }));
145 }
146 pub fn add_capsule(&mut self, transform: LocalTransform, radius: f64, half_height: f64) {
148 self.children.push((
149 transform,
150 ChildShapeKind::Capsule {
151 radius,
152 half_height,
153 },
154 ));
155 }
156 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
160 if self.children.is_empty() {
161 return ([0.0; 3], [0.0; 3]);
162 }
163 let mut min = [f64::INFINITY; 3];
164 let mut max = [f64::NEG_INFINITY; 3];
165 for (transform, kind) in &self.children {
166 let local_he = match kind {
167 ChildShapeKind::Sphere { radius } => [*radius, *radius, *radius],
168 ChildShapeKind::Box { half_extents } => *half_extents,
169 ChildShapeKind::Capsule {
170 radius,
171 half_height,
172 } => [*radius, half_height + radius, *radius],
173 };
174 let corners_local = [
175 [-local_he[0], -local_he[1], -local_he[2]],
176 [local_he[0], -local_he[1], -local_he[2]],
177 [-local_he[0], local_he[1], -local_he[2]],
178 [local_he[0], local_he[1], -local_he[2]],
179 [-local_he[0], -local_he[1], local_he[2]],
180 [local_he[0], -local_he[1], local_he[2]],
181 [-local_he[0], local_he[1], local_he[2]],
182 [local_he[0], local_he[1], local_he[2]],
183 ];
184 for corner in &corners_local {
185 let w = transform.local_to_world(*corner);
186 for i in 0..3 {
187 if w[i] < min[i] {
188 min[i] = w[i];
189 }
190 if w[i] > max[i] {
191 max[i] = w[i];
192 }
193 }
194 }
195 }
196 (min, max)
197 }
198 pub fn contains_point(&self, p: [f64; 3]) -> bool {
200 for (transform, kind) in &self.children {
201 let local_p = transform.world_to_local(p);
202 if child_kind_contains(kind, local_p) {
203 return true;
204 }
205 }
206 false
207 }
208 pub fn ray_cast(&self, origin: [f64; 3], dir: [f64; 3]) -> Option<(f64, [f64; 3])> {
210 let mut best: Option<(f64, [f64; 3])> = None;
211 for (transform, kind) in &self.children {
212 let local_o = transform.world_to_local(origin);
213 let local_d = transform.world_to_local_dir(dir);
214 if let Some((t, local_n)) = ray_cast_kind(kind, local_o, local_d, f64::MAX * 0.5) {
215 let world_n = transform.local_to_world_dir(local_n);
216 if best.as_ref().is_none_or(|(bt, _)| t < *bt) {
217 best = Some((t, world_n));
218 }
219 }
220 }
221 best
222 }
223 pub fn volume(&self) -> f64 {
225 self.children
226 .iter()
227 .map(|(_, k)| CompoundShape::child_volume(k))
228 .sum()
229 }
230 pub fn inertia_tensor(&self, density: f64) -> [[f64; 3]; 3] {
234 let mut i_xx = 0.0f64;
235 let mut i_yy = 0.0f64;
236 let mut i_zz = 0.0f64;
237 let mut i_xy = 0.0f64;
238 let mut i_xz = 0.0f64;
239 let mut i_yz = 0.0f64;
240 for (transform, kind) in &self.children {
241 let vol = CompoundShape::child_volume(kind);
242 let m = density * vol;
243 let (lxx, lyy, lzz) = CompoundShape::child_local_inertia(kind, m);
244 let r = transform.local_to_world([0.0; 3]);
245 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
246 i_xx += lxx + m * (r2 - r[0] * r[0]);
247 i_yy += lyy + m * (r2 - r[1] * r[1]);
248 i_zz += lzz + m * (r2 - r[2] * r[2]);
249 i_xy -= m * r[0] * r[1];
250 i_xz -= m * r[0] * r[2];
251 i_yz -= m * r[1] * r[2];
252 }
253 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
254 }
255}
256#[derive(Debug, Clone)]
258pub struct Compound {
259 pub children: Vec<(Transform, Arc<dyn Shape>)>,
261}
262impl Compound {
263 pub fn new(children: Vec<(Transform, Arc<dyn Shape>)>) -> Self {
265 Self { children }
266 }
267}
268#[derive(Debug, Clone)]
273pub struct CompoundShape {
274 pub children: Vec<CompoundChild>,
276}
277impl Default for CompoundShape {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282impl CompoundShape {
283 pub fn new() -> Self {
285 Self {
286 children: Vec::new(),
287 }
288 }
289 pub fn add_sphere(&mut self, center: [f64; 3], radius: f64) {
291 self.children.push(CompoundChild {
292 center,
293 shape_kind: ChildShapeKind::Sphere { radius },
294 });
295 }
296 pub fn add_box(&mut self, center: [f64; 3], half_extents: [f64; 3]) {
298 self.children.push(CompoundChild {
299 center,
300 shape_kind: ChildShapeKind::Box { half_extents },
301 });
302 }
303 pub fn add_capsule(&mut self, center: [f64; 3], radius: f64, half_height: f64) {
305 self.children.push(CompoundChild {
306 center,
307 shape_kind: ChildShapeKind::Capsule {
308 radius,
309 half_height,
310 },
311 });
312 }
313 pub fn child_count(&self) -> usize {
315 self.children.len()
316 }
317 fn child_volume(kind: &ChildShapeKind) -> f64 {
319 match kind {
320 ChildShapeKind::Sphere { radius } => {
321 (4.0 / 3.0) * std::f64::consts::PI * radius * radius * radius
322 }
323 ChildShapeKind::Box { half_extents } => {
324 8.0 * half_extents[0] * half_extents[1] * half_extents[2]
325 }
326 ChildShapeKind::Capsule {
327 radius,
328 half_height,
329 } => {
330 let sphere_vol = (4.0 / 3.0) * std::f64::consts::PI * radius * radius * radius;
331 let cyl_vol = std::f64::consts::PI * radius * radius * 2.0 * half_height;
332 sphere_vol + cyl_vol
333 }
334 }
335 }
336 pub fn total_volume(&self) -> f64 {
338 self.children
339 .iter()
340 .map(|c| Self::child_volume(&c.shape_kind))
341 .sum()
342 }
343 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
347 if self.children.is_empty() {
348 return ([0.0; 3], [0.0; 3]);
349 }
350 let mut min = [f64::INFINITY; 3];
351 let mut max = [f64::NEG_INFINITY; 3];
352 for child in &self.children {
353 let (cmin, cmax) = Self::child_aabb(child);
354 for i in 0..3 {
355 if cmin[i] < min[i] {
356 min[i] = cmin[i];
357 }
358 if cmax[i] > max[i] {
359 max[i] = cmax[i];
360 }
361 }
362 }
363 (min, max)
364 }
365 fn child_aabb(child: &CompoundChild) -> ([f64; 3], [f64; 3]) {
367 let c = child.center;
368 match child.shape_kind {
369 ChildShapeKind::Sphere { radius } => (
370 [c[0] - radius, c[1] - radius, c[2] - radius],
371 [c[0] + radius, c[1] + radius, c[2] + radius],
372 ),
373 ChildShapeKind::Box { half_extents } => (
374 [
375 c[0] - half_extents[0],
376 c[1] - half_extents[1],
377 c[2] - half_extents[2],
378 ],
379 [
380 c[0] + half_extents[0],
381 c[1] + half_extents[1],
382 c[2] + half_extents[2],
383 ],
384 ),
385 ChildShapeKind::Capsule {
386 radius,
387 half_height,
388 } => (
389 [c[0] - radius, c[1] - half_height - radius, c[2] - radius],
390 [c[0] + radius, c[1] + half_height + radius, c[2] + radius],
391 ),
392 }
393 }
394 pub fn center_of_mass(&self) -> [f64; 3] {
396 let total_vol = self.total_volume();
397 if total_vol < 1e-12 {
398 return [0.0; 3];
399 }
400 let mut com = [0.0; 3];
401 for child in &self.children {
402 let v = Self::child_volume(&child.shape_kind);
403 for (com_i, c_i) in com.iter_mut().zip(child.center.iter()) {
404 *com_i += c_i * v;
405 }
406 }
407 for com_i in com.iter_mut() {
408 *com_i /= total_vol;
409 }
410 com
411 }
412 pub fn contains_point(&self, p: [f64; 3]) -> bool {
414 for child in &self.children {
415 if Self::child_contains(child, p) {
416 return true;
417 }
418 }
419 false
420 }
421 fn child_contains(child: &CompoundChild, p: [f64; 3]) -> bool {
423 let dx = p[0] - child.center[0];
424 let dy = p[1] - child.center[1];
425 let dz = p[2] - child.center[2];
426 match child.shape_kind {
427 ChildShapeKind::Sphere { radius } => dx * dx + dy * dy + dz * dz <= radius * radius,
428 ChildShapeKind::Box { half_extents } => {
429 dx.abs() <= half_extents[0]
430 && dy.abs() <= half_extents[1]
431 && dz.abs() <= half_extents[2]
432 }
433 ChildShapeKind::Capsule {
434 radius,
435 half_height,
436 } => {
437 let clamped_y = dy.clamp(-half_height, half_height);
438 let ry = dy - clamped_y;
439 dx * dx + ry * ry + dz * dz <= radius * radius
440 }
441 }
442 }
443 pub fn ray_cast(
446 &self,
447 origin: [f64; 3],
448 dir: [f64; 3],
449 max_toi: f64,
450 ) -> Option<(f64, [f64; 3], usize)> {
451 let mut best: Option<(f64, [f64; 3], usize)> = None;
452 for (idx, child) in self.children.iter().enumerate() {
453 if let Some((t, n)) = Self::ray_cast_child(child, origin, dir, max_toi)
454 && best.as_ref().is_none_or(|(bt, _, _)| t < *bt)
455 {
456 best = Some((t, n, idx));
457 }
458 }
459 best
460 }
461 fn ray_cast_child(
463 child: &CompoundChild,
464 origin: [f64; 3],
465 dir: [f64; 3],
466 max_toi: f64,
467 ) -> Option<(f64, [f64; 3])> {
468 let lo = [
469 origin[0] - child.center[0],
470 origin[1] - child.center[1],
471 origin[2] - child.center[2],
472 ];
473 match child.shape_kind {
474 ChildShapeKind::Sphere { radius } => ray_sphere(lo, dir, radius, max_toi),
475 ChildShapeKind::Box { half_extents } => ray_box(lo, dir, half_extents, max_toi),
476 ChildShapeKind::Capsule {
477 radius,
478 half_height,
479 } => ray_capsule(lo, dir, radius, half_height, max_toi),
480 }
481 }
482}
483impl CompoundShape {
484 pub fn inertia_tensor(&self, density: f64) -> [[f64; 3]; 3] {
488 let com = self.center_of_mass();
489 let mut i_xx = 0.0f64;
490 let mut i_yy = 0.0f64;
491 let mut i_zz = 0.0f64;
492 let mut i_xy = 0.0f64;
493 let mut i_xz = 0.0f64;
494 let mut i_yz = 0.0f64;
495 for child in &self.children {
496 let vol = Self::child_volume(&child.shape_kind);
497 let m = density * vol;
498 let (lxx, lyy, lzz) = Self::child_local_inertia(&child.shape_kind, m);
499 let r = [
500 child.center[0] - com[0],
501 child.center[1] - com[1],
502 child.center[2] - com[2],
503 ];
504 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
505 i_xx += lxx + m * (r2 - r[0] * r[0]);
506 i_yy += lyy + m * (r2 - r[1] * r[1]);
507 i_zz += lzz + m * (r2 - r[2] * r[2]);
508 i_xy -= m * r[0] * r[1];
509 i_xz -= m * r[0] * r[2];
510 i_yz -= m * r[1] * r[2];
511 }
512 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
513 }
514 fn child_local_inertia(kind: &ChildShapeKind, mass: f64) -> (f64, f64, f64) {
516 match kind {
517 ChildShapeKind::Sphere { radius } => {
518 let i = 2.0 / 5.0 * mass * radius * radius;
519 (i, i, i)
520 }
521 ChildShapeKind::Box { half_extents } => {
522 let [hx, hy, hz] = *half_extents;
523 let i_xx = mass / 3.0 * (hy * hy + hz * hz);
524 let i_yy = mass / 3.0 * (hx * hx + hz * hz);
525 let i_zz = mass / 3.0 * (hx * hx + hy * hy);
526 (i_xx, i_yy, i_zz)
527 }
528 ChildShapeKind::Capsule {
529 radius,
530 half_height,
531 } => {
532 let r = radius;
533 let h = half_height * 2.0;
534 let m_cyl = mass * std::f64::consts::PI * r * r * h
535 / (std::f64::consts::PI * r * r * h
536 + (4.0 / 3.0) * std::f64::consts::PI * r * r * r);
537 let m_hemi = (mass - m_cyl) / 2.0;
538 let i_cyl_xx = m_cyl * (3.0 * r * r + h * h) / 12.0;
539 let i_hemi_xx = m_hemi * (2.0 * r * r / 5.0 + (3.0 * half_height / 8.0).powi(2));
540 let i_xx = i_cyl_xx + 2.0 * i_hemi_xx;
541 let i_yy = m_cyl * r * r / 2.0 + 2.0 * m_hemi * 2.0 * r * r / 5.0;
542 (i_xx, i_yy, i_xx)
543 }
544 }
545 }
546 pub fn bounding_sphere(&self) -> ([f64; 3], f64) {
548 let com = self.center_of_mass();
549 let mut max_r = 0.0f64;
550 for child in &self.children {
551 let child_r = self.child_bounding_radius(child);
552 let dist_to_com = {
553 let dx = child.center[0] - com[0];
554 let dy = child.center[1] - com[1];
555 let dz = child.center[2] - com[2];
556 (dx * dx + dy * dy + dz * dz).sqrt()
557 };
558 let r = dist_to_com + child_r;
559 if r > max_r {
560 max_r = r;
561 }
562 }
563 (com, max_r)
564 }
565 fn child_bounding_radius(&self, child: &CompoundChild) -> f64 {
566 match child.shape_kind {
567 ChildShapeKind::Sphere { radius } => radius,
568 ChildShapeKind::Box { half_extents } => {
569 (half_extents[0].powi(2) + half_extents[1].powi(2) + half_extents[2].powi(2)).sqrt()
570 }
571 ChildShapeKind::Capsule {
572 radius,
573 half_height,
574 } => half_height + radius,
575 }
576 }
577 pub fn scale(&mut self, factor: f64) {
579 for child in &mut self.children {
580 child.center[0] *= factor;
581 child.center[1] *= factor;
582 child.center[2] *= factor;
583 match &mut child.shape_kind {
584 ChildShapeKind::Sphere { radius } => *radius *= factor,
585 ChildShapeKind::Box { half_extents } => {
586 half_extents[0] *= factor;
587 half_extents[1] *= factor;
588 half_extents[2] *= factor;
589 }
590 ChildShapeKind::Capsule {
591 radius,
592 half_height,
593 } => {
594 *radius *= factor;
595 *half_height *= factor;
596 }
597 }
598 }
599 }
600 pub fn translate(&mut self, offset: [f64; 3]) {
602 for child in &mut self.children {
603 child.center[0] += offset[0];
604 child.center[1] += offset[1];
605 child.center[2] += offset[2];
606 }
607 }
608 pub fn merge_with(&self, other: &CompoundShape) -> CompoundShape {
610 let mut result = self.clone();
611 result.children.extend(other.children.iter().cloned());
612 result
613 }
614 pub fn overlaps_sphere(&self, center: [f64; 3], radius: f64) -> bool {
618 for child in &self.children {
619 let dx = child.center[0] - center[0];
620 let dy = child.center[1] - center[1];
621 let dz = child.center[2] - center[2];
622 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
623 let child_r = self.child_bounding_radius(child);
624 if dist < child_r + radius {
625 return true;
626 }
627 }
628 false
629 }
630 pub fn ray_cast_all(
632 &self,
633 origin: [f64; 3],
634 dir: [f64; 3],
635 max_toi: f64,
636 ) -> Vec<(f64, [f64; 3], usize)> {
637 let mut hits: Vec<(f64, [f64; 3], usize)> = Vec::new();
638 for (idx, child) in self.children.iter().enumerate() {
639 if let Some((t, n)) = Self::ray_cast_child(child, origin, dir, max_toi) {
640 hits.push((t, n, idx));
641 }
642 }
643 hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
644 hits
645 }
646}
647impl CompoundShape {
648 pub fn merged_aabb(&self) -> ([f64; 3], [f64; 3]) {
650 self.aabb()
651 }
652 pub fn raycast(
656 &self,
657 ray_origin: [f64; 3],
658 ray_dir: [f64; 3],
659 max_t: f64,
660 ) -> Option<(f64, usize)> {
661 self.ray_cast(ray_origin, ray_dir, max_t)
662 .map(|(t, _n, idx)| (t, idx))
663 }
664 pub fn volume(&self) -> f64 {
666 self.total_volume()
667 }
668 pub fn center_of_mass_weighted(&self, masses: &[f64]) -> [f64; 3] {
673 let total: f64 = masses.iter().copied().take(self.children.len()).sum();
674 if total < 1e-30 {
675 return [0.0; 3];
676 }
677 let mut com = [0.0f64; 3];
678 for (i, child) in self.children.iter().enumerate() {
679 let m = if i < masses.len() { masses[i] } else { 0.0 };
680 for (com_k, c_k) in com.iter_mut().zip(child.center.iter()) {
681 *com_k += m * c_k;
682 }
683 }
684 for com_k in com.iter_mut() {
685 *com_k /= total;
686 }
687 com
688 }
689 pub fn inertia_tensor_from_masses(&self, masses: &[f64]) -> [[f64; 3]; 3] {
693 let com = self.center_of_mass_weighted(masses);
694 let mut i_xx = 0.0f64;
695 let mut i_yy = 0.0f64;
696 let mut i_zz = 0.0f64;
697 let mut i_xy = 0.0f64;
698 let mut i_xz = 0.0f64;
699 let mut i_yz = 0.0f64;
700 for (idx, child) in self.children.iter().enumerate() {
701 let m = if idx < masses.len() { masses[idx] } else { 0.0 };
702 let (lxx, lyy, lzz) = Self::child_local_inertia(&child.shape_kind, m);
703 let r = [
704 child.center[0] - com[0],
705 child.center[1] - com[1],
706 child.center[2] - com[2],
707 ];
708 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
709 i_xx += lxx + m * (r2 - r[0] * r[0]);
710 i_yy += lyy + m * (r2 - r[1] * r[1]);
711 i_zz += lzz + m * (r2 - r[2] * r[2]);
712 i_xy -= m * r[0] * r[1];
713 i_xz -= m * r[0] * r[2];
714 i_yz -= m * r[1] * r[2];
715 }
716 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
717 }
718 pub fn closest_point(&self, p: [f64; 3]) -> ([f64; 3], usize) {
722 let mut best_dist = f64::INFINITY;
723 let mut best_pt = p;
724 let mut best_idx = 0usize;
725 for (i, child) in self.children.iter().enumerate() {
726 let cp = Self::child_closest_point(child, p);
727 let dx = cp[0] - p[0];
728 let dy = cp[1] - p[1];
729 let dz = cp[2] - p[2];
730 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
731 if dist < best_dist {
732 best_dist = dist;
733 best_pt = cp;
734 best_idx = i;
735 }
736 }
737 (best_pt, best_idx)
738 }
739 fn child_closest_point(child: &CompoundChild, p: [f64; 3]) -> [f64; 3] {
740 let c = child.center;
741 match child.shape_kind {
742 ChildShapeKind::Sphere { radius } => {
743 let dx = p[0] - c[0];
744 let dy = p[1] - c[1];
745 let dz = p[2] - c[2];
746 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
747 if dist < 1e-30 {
748 [c[0] + radius, c[1], c[2]]
749 } else {
750 let scale = radius / dist;
751 [c[0] + dx * scale, c[1] + dy * scale, c[2] + dz * scale]
752 }
753 }
754 ChildShapeKind::Box { half_extents } => {
755 let clamped = [
756 (p[0] - c[0]).clamp(-half_extents[0], half_extents[0]) + c[0],
757 (p[1] - c[1]).clamp(-half_extents[1], half_extents[1]) + c[1],
758 (p[2] - c[2]).clamp(-half_extents[2], half_extents[2]) + c[2],
759 ];
760 let inside = (0..3).all(|i| {
761 let he = [half_extents[0], half_extents[1], half_extents[2]][i];
762 let d = [p[0] - c[0], p[1] - c[1], p[2] - c[2]][i].abs();
763 d <= he
764 });
765 if inside {
766 let dx_neg = [half_extents[0], half_extents[1], half_extents[2]][0]
767 - (p[0] - c[0]).abs();
768 let dy_neg = [half_extents[0], half_extents[1], half_extents[2]][1]
769 - (p[1] - c[1]).abs();
770 let dz_neg = [half_extents[0], half_extents[1], half_extents[2]][2]
771 - (p[2] - c[2]).abs();
772 if dx_neg <= dy_neg && dx_neg <= dz_neg {
773 let sx = if p[0] >= c[0] { 1.0 } else { -1.0 };
774 [c[0] + half_extents[0] * sx, p[1], p[2]]
775 } else if dy_neg <= dx_neg && dy_neg <= dz_neg {
776 let sy = if p[1] >= c[1] { 1.0 } else { -1.0 };
777 [p[0], c[1] + half_extents[1] * sy, p[2]]
778 } else {
779 let sz = if p[2] >= c[2] { 1.0 } else { -1.0 };
780 [p[0], p[1], c[2] + half_extents[2] * sz]
781 }
782 } else {
783 clamped
784 }
785 }
786 ChildShapeKind::Capsule {
787 radius,
788 half_height,
789 } => {
790 let dy = p[1] - c[1];
791 let clamped_y = dy.clamp(-half_height, half_height);
792 let axis_pt = [c[0], c[1] + clamped_y, c[2]];
793 let dx = p[0] - axis_pt[0];
794 let dpz = p[2] - axis_pt[2];
795 let dist_xz = (dx * dx + dpz * dpz).sqrt();
796 if dist_xz < 1e-30 {
797 [axis_pt[0] + radius, axis_pt[1], axis_pt[2]]
798 } else {
799 let scale = radius / dist_xz;
800 [
801 axis_pt[0] + dx * scale,
802 axis_pt[1],
803 axis_pt[2] + dpz * scale,
804 ]
805 }
806 }
807 }
808 }
809}
810impl CompoundShape {
811 pub fn remove_child(&mut self, index: usize) {
813 self.children.remove(index);
814 }
815 pub fn swap_remove_child(&mut self, index: usize) {
820 self.children.swap_remove(index);
821 }
822 pub fn replace_with_sphere(&mut self, index: usize, center: [f64; 3], radius: f64) {
824 self.children[index] = CompoundChild {
825 center,
826 shape_kind: ChildShapeKind::Sphere { radius },
827 };
828 }
829 pub fn replace_with_box(&mut self, index: usize, center: [f64; 3], half_extents: [f64; 3]) {
831 self.children[index] = CompoundChild {
832 center,
833 shape_kind: ChildShapeKind::Box { half_extents },
834 };
835 }
836 pub fn is_empty(&self) -> bool {
838 self.children.is_empty()
839 }
840 pub fn clear(&mut self) {
842 self.children.clear();
843 }
844 pub fn closest_point_with_dist2(&self, p: [f64; 3]) -> ([f64; 3], f64, usize) {
849 let mut best_dist2 = f64::INFINITY;
850 let mut best_pt = p;
851 let mut best_idx = 0usize;
852 for (i, child) in self.children.iter().enumerate() {
853 let cp = Self::child_closest_point(child, p);
854 let dx = cp[0] - p[0];
855 let dy = cp[1] - p[1];
856 let dz = cp[2] - p[2];
857 let d2 = dx * dx + dy * dy + dz * dz;
858 if d2 < best_dist2 {
859 best_dist2 = d2;
860 best_pt = cp;
861 best_idx = i;
862 }
863 }
864 (best_pt, best_dist2, best_idx)
865 }
866 pub fn broad_phase_pairs(&self, other: &CompoundShape) -> Vec<(usize, usize)> {
871 let mut pairs = Vec::new();
872 for (i, ci) in self.children.iter().enumerate() {
873 let ri = self.child_bounding_radius(ci);
874 for (j, cj) in other.children.iter().enumerate() {
875 let rj = other.child_bounding_radius(cj);
876 let dx = ci.center[0] - cj.center[0];
877 let dy = ci.center[1] - cj.center[1];
878 let dz = ci.center[2] - cj.center[2];
879 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
880 if dist < ri + rj {
881 pairs.push((i, j));
882 }
883 }
884 }
885 pairs
886 }
887 pub fn overlaps_compound(&self, other: &CompoundShape) -> bool {
889 !self.broad_phase_pairs(other).is_empty()
890 }
891 pub fn centroid_with_densities(&self, densities: &[f64]) -> [f64; 3] {
897 let mut total_mass = 0.0f64;
898 let mut com = [0.0f64; 3];
899 for (i, child) in self.children.iter().enumerate() {
900 let rho = if i < densities.len() {
901 densities[i]
902 } else {
903 1.0
904 };
905 let vol = Self::child_volume(&child.shape_kind);
906 let m = rho * vol;
907 total_mass += m;
908 for (com_k, c_k) in com.iter_mut().zip(child.center.iter()) {
909 *com_k += m * c_k;
910 }
911 }
912 if total_mass < 1e-30 {
913 return [0.0; 3];
914 }
915 for com_k in com.iter_mut() {
916 *com_k /= total_mass;
917 }
918 com
919 }
920 pub fn penetration_depth_sphere(&self, center: [f64; 3], radius: f64) -> Option<(f64, usize)> {
929 let mut best: Option<(f64, usize)> = None;
930 for (i, child) in self.children.iter().enumerate() {
931 let signed = self.signed_distance_child(child, center, radius);
932 if signed < 0.0 && best.as_ref().is_none_or(|(bd, _)| signed < *bd) {
933 best = Some((signed, i));
934 }
935 }
936 best
937 }
938 fn signed_distance_child(
942 &self,
943 child: &CompoundChild,
944 sphere_center: [f64; 3],
945 sphere_radius: f64,
946 ) -> f64 {
947 let cp = Self::child_closest_point(child, sphere_center);
948 let dx = cp[0] - sphere_center[0];
949 let dy = cp[1] - sphere_center[1];
950 let dz = cp[2] - sphere_center[2];
951 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
952 dist - sphere_radius
953 }
954 pub fn child_masses(&self, density: f64) -> Vec<f64> {
956 self.children
957 .iter()
958 .map(|c| density * Self::child_volume(&c.shape_kind))
959 .collect()
960 }
961 pub fn total_mass(&self, density: f64) -> f64 {
963 density * self.total_volume()
964 }
965 pub fn child_aabb_public(child: &CompoundChild) -> ([f64; 3], [f64; 3]) {
967 Self::child_aabb(child)
968 }
969 pub fn expanded_aabb(&self, margin: f64) -> ([f64; 3], [f64; 3]) {
971 let (mn, mx) = self.aabb();
972 (
973 [mn[0] - margin, mn[1] - margin, mn[2] - margin],
974 [mx[0] + margin, mx[1] + margin, mx[2] + margin],
975 )
976 }
977 pub fn sphere_overlaps_aabb(&self, center: [f64; 3], radius: f64) -> bool {
979 if self.children.is_empty() {
980 return false;
981 }
982 let (mn, mx) = self.aabb();
983 let cx = center[0].clamp(mn[0], mx[0]);
984 let cy = center[1].clamp(mn[1], mx[1]);
985 let cz = center[2].clamp(mn[2], mx[2]);
986 let dx = cx - center[0];
987 let dy = cy - center[1];
988 let dz = cz - center[2];
989 dx * dx + dy * dy + dz * dz <= radius * radius
990 }
991}