1#![allow(clippy::needless_range_loop)]
6use super::functions::*;
7use crate::shape::Shape;
8use oxiphysics_core::Transform;
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Copy)]
13#[allow(dead_code)]
14pub enum ChildShapeKind {
15 Sphere {
17 radius: f64,
19 },
20 Box {
22 half_extents: [f64; 3],
24 },
25 Capsule {
27 radius: f64,
29 half_height: f64,
31 },
32}
33#[derive(Debug, Clone)]
35#[allow(dead_code)]
36pub struct CompoundChild {
37 pub center: [f64; 3],
39 pub shape_kind: ChildShapeKind,
41}
42#[derive(Debug, Clone)]
44#[allow(dead_code)]
45pub struct CompoundAabb {
46 pub all_aabbs: Vec<([f64; 3], [f64; 3])>,
48 pub merged_min: [f64; 3],
50 pub merged_max: [f64; 3],
52}
53#[derive(Debug, Clone)]
58#[allow(dead_code)]
59pub struct LocalTransform {
60 pub translation: [f64; 3],
62 pub rot: [[f64; 3]; 3],
64}
65#[allow(dead_code)]
66impl LocalTransform {
67 pub fn identity() -> Self {
69 Self {
70 translation: [0.0; 3],
71 rot: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
72 }
73 }
74 pub fn from_translation(t: [f64; 3]) -> Self {
76 Self {
77 translation: t,
78 rot: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
79 }
80 }
81 pub fn local_to_world(&self, p: [f64; 3]) -> [f64; 3] {
85 let mut out = self.translation;
86 for i in 0..3 {
87 out[i] += self.rot[i][0] * p[0] + self.rot[i][1] * p[1] + self.rot[i][2] * p[2];
88 }
89 out
90 }
91 pub fn world_to_local(&self, p: [f64; 3]) -> [f64; 3] {
95 let d = [
96 p[0] - self.translation[0],
97 p[1] - self.translation[1],
98 p[2] - self.translation[2],
99 ];
100 [
101 self.rot[0][0] * d[0] + self.rot[1][0] * d[1] + self.rot[2][0] * d[2],
102 self.rot[0][1] * d[0] + self.rot[1][1] * d[1] + self.rot[2][1] * d[2],
103 self.rot[0][2] * d[0] + self.rot[1][2] * d[1] + self.rot[2][2] * d[2],
104 ]
105 }
106 pub fn local_to_world_dir(&self, v: [f64; 3]) -> [f64; 3] {
108 [
109 self.rot[0][0] * v[0] + self.rot[0][1] * v[1] + self.rot[0][2] * v[2],
110 self.rot[1][0] * v[0] + self.rot[1][1] * v[1] + self.rot[1][2] * v[2],
111 self.rot[2][0] * v[0] + self.rot[2][1] * v[1] + self.rot[2][2] * v[2],
112 ]
113 }
114 pub fn world_to_local_dir(&self, v: [f64; 3]) -> [f64; 3] {
116 [
117 self.rot[0][0] * v[0] + self.rot[1][0] * v[1] + self.rot[2][0] * v[2],
118 self.rot[0][1] * v[0] + self.rot[1][1] * v[1] + self.rot[2][1] * v[2],
119 self.rot[0][2] * v[0] + self.rot[1][2] * v[1] + self.rot[2][2] * v[2],
120 ]
121 }
122}
123#[derive(Debug, Clone)]
126#[allow(dead_code)]
127pub struct CompoundShapeEx {
128 pub children: Vec<(LocalTransform, ChildShapeKind)>,
130}
131impl Default for CompoundShapeEx {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136#[allow(dead_code)]
137impl CompoundShapeEx {
138 pub fn new() -> Self {
140 Self {
141 children: Vec::new(),
142 }
143 }
144 pub fn add_sphere(&mut self, transform: LocalTransform, radius: f64) {
146 self.children
147 .push((transform, ChildShapeKind::Sphere { radius }));
148 }
149 pub fn add_box(&mut self, transform: LocalTransform, half_extents: [f64; 3]) {
151 self.children
152 .push((transform, ChildShapeKind::Box { half_extents }));
153 }
154 pub fn add_capsule(&mut self, transform: LocalTransform, radius: f64, half_height: f64) {
156 self.children.push((
157 transform,
158 ChildShapeKind::Capsule {
159 radius,
160 half_height,
161 },
162 ));
163 }
164 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
168 if self.children.is_empty() {
169 return ([0.0; 3], [0.0; 3]);
170 }
171 let mut min = [f64::INFINITY; 3];
172 let mut max = [f64::NEG_INFINITY; 3];
173 for (transform, kind) in &self.children {
174 let local_he = match kind {
175 ChildShapeKind::Sphere { radius } => [*radius, *radius, *radius],
176 ChildShapeKind::Box { half_extents } => *half_extents,
177 ChildShapeKind::Capsule {
178 radius,
179 half_height,
180 } => [*radius, half_height + radius, *radius],
181 };
182 let corners_local = [
183 [-local_he[0], -local_he[1], -local_he[2]],
184 [local_he[0], -local_he[1], -local_he[2]],
185 [-local_he[0], local_he[1], -local_he[2]],
186 [local_he[0], local_he[1], -local_he[2]],
187 [-local_he[0], -local_he[1], local_he[2]],
188 [local_he[0], -local_he[1], local_he[2]],
189 [-local_he[0], local_he[1], local_he[2]],
190 [local_he[0], local_he[1], local_he[2]],
191 ];
192 for corner in &corners_local {
193 let w = transform.local_to_world(*corner);
194 for i in 0..3 {
195 if w[i] < min[i] {
196 min[i] = w[i];
197 }
198 if w[i] > max[i] {
199 max[i] = w[i];
200 }
201 }
202 }
203 }
204 (min, max)
205 }
206 pub fn contains_point(&self, p: [f64; 3]) -> bool {
208 for (transform, kind) in &self.children {
209 let local_p = transform.world_to_local(p);
210 if child_kind_contains(kind, local_p) {
211 return true;
212 }
213 }
214 false
215 }
216 pub fn ray_cast(&self, origin: [f64; 3], dir: [f64; 3]) -> Option<(f64, [f64; 3])> {
218 let mut best: Option<(f64, [f64; 3])> = None;
219 for (transform, kind) in &self.children {
220 let local_o = transform.world_to_local(origin);
221 let local_d = transform.world_to_local_dir(dir);
222 if let Some((t, local_n)) = ray_cast_kind(kind, local_o, local_d, f64::MAX * 0.5) {
223 let world_n = transform.local_to_world_dir(local_n);
224 if best.as_ref().is_none_or(|(bt, _)| t < *bt) {
225 best = Some((t, world_n));
226 }
227 }
228 }
229 best
230 }
231 pub fn volume(&self) -> f64 {
233 self.children
234 .iter()
235 .map(|(_, k)| CompoundShape::child_volume(k))
236 .sum()
237 }
238 #[allow(clippy::too_many_arguments)]
242 pub fn inertia_tensor(&self, density: f64) -> [[f64; 3]; 3] {
243 let mut i_xx = 0.0f64;
244 let mut i_yy = 0.0f64;
245 let mut i_zz = 0.0f64;
246 let mut i_xy = 0.0f64;
247 let mut i_xz = 0.0f64;
248 let mut i_yz = 0.0f64;
249 for (transform, kind) in &self.children {
250 let vol = CompoundShape::child_volume(kind);
251 let m = density * vol;
252 let (lxx, lyy, lzz) = CompoundShape::child_local_inertia(kind, m);
253 let r = transform.local_to_world([0.0; 3]);
254 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
255 i_xx += lxx + m * (r2 - r[0] * r[0]);
256 i_yy += lyy + m * (r2 - r[1] * r[1]);
257 i_zz += lzz + m * (r2 - r[2] * r[2]);
258 i_xy -= m * r[0] * r[1];
259 i_xz -= m * r[0] * r[2];
260 i_yz -= m * r[1] * r[2];
261 }
262 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
263 }
264}
265#[derive(Debug, Clone)]
267pub struct Compound {
268 pub children: Vec<(Transform, Arc<dyn Shape>)>,
270}
271impl Compound {
272 pub fn new(children: Vec<(Transform, Arc<dyn Shape>)>) -> Self {
274 Self { children }
275 }
276}
277#[derive(Debug, Clone)]
282#[allow(dead_code)]
283pub struct CompoundShape {
284 pub children: Vec<CompoundChild>,
286}
287impl Default for CompoundShape {
288 fn default() -> Self {
289 Self::new()
290 }
291}
292#[allow(dead_code)]
293impl CompoundShape {
294 pub fn new() -> Self {
296 Self {
297 children: Vec::new(),
298 }
299 }
300 pub fn add_sphere(&mut self, center: [f64; 3], radius: f64) {
302 self.children.push(CompoundChild {
303 center,
304 shape_kind: ChildShapeKind::Sphere { radius },
305 });
306 }
307 pub fn add_box(&mut self, center: [f64; 3], half_extents: [f64; 3]) {
309 self.children.push(CompoundChild {
310 center,
311 shape_kind: ChildShapeKind::Box { half_extents },
312 });
313 }
314 pub fn add_capsule(&mut self, center: [f64; 3], radius: f64, half_height: f64) {
316 self.children.push(CompoundChild {
317 center,
318 shape_kind: ChildShapeKind::Capsule {
319 radius,
320 half_height,
321 },
322 });
323 }
324 pub fn child_count(&self) -> usize {
326 self.children.len()
327 }
328 fn child_volume(kind: &ChildShapeKind) -> f64 {
330 match kind {
331 ChildShapeKind::Sphere { radius } => {
332 (4.0 / 3.0) * std::f64::consts::PI * radius * radius * radius
333 }
334 ChildShapeKind::Box { half_extents } => {
335 8.0 * half_extents[0] * half_extents[1] * half_extents[2]
336 }
337 ChildShapeKind::Capsule {
338 radius,
339 half_height,
340 } => {
341 let sphere_vol = (4.0 / 3.0) * std::f64::consts::PI * radius * radius * radius;
342 let cyl_vol = std::f64::consts::PI * radius * radius * 2.0 * half_height;
343 sphere_vol + cyl_vol
344 }
345 }
346 }
347 pub fn total_volume(&self) -> f64 {
349 self.children
350 .iter()
351 .map(|c| Self::child_volume(&c.shape_kind))
352 .sum()
353 }
354 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
358 if self.children.is_empty() {
359 return ([0.0; 3], [0.0; 3]);
360 }
361 let mut min = [f64::INFINITY; 3];
362 let mut max = [f64::NEG_INFINITY; 3];
363 for child in &self.children {
364 let (cmin, cmax) = Self::child_aabb(child);
365 for i in 0..3 {
366 if cmin[i] < min[i] {
367 min[i] = cmin[i];
368 }
369 if cmax[i] > max[i] {
370 max[i] = cmax[i];
371 }
372 }
373 }
374 (min, max)
375 }
376 fn child_aabb(child: &CompoundChild) -> ([f64; 3], [f64; 3]) {
378 let c = child.center;
379 match child.shape_kind {
380 ChildShapeKind::Sphere { radius } => (
381 [c[0] - radius, c[1] - radius, c[2] - radius],
382 [c[0] + radius, c[1] + radius, c[2] + radius],
383 ),
384 ChildShapeKind::Box { half_extents } => (
385 [
386 c[0] - half_extents[0],
387 c[1] - half_extents[1],
388 c[2] - half_extents[2],
389 ],
390 [
391 c[0] + half_extents[0],
392 c[1] + half_extents[1],
393 c[2] + half_extents[2],
394 ],
395 ),
396 ChildShapeKind::Capsule {
397 radius,
398 half_height,
399 } => (
400 [c[0] - radius, c[1] - half_height - radius, c[2] - radius],
401 [c[0] + radius, c[1] + half_height + radius, c[2] + radius],
402 ),
403 }
404 }
405 pub fn center_of_mass(&self) -> [f64; 3] {
407 let total_vol = self.total_volume();
408 if total_vol < 1e-12 {
409 return [0.0; 3];
410 }
411 let mut com = [0.0; 3];
412 for child in &self.children {
413 let v = Self::child_volume(&child.shape_kind);
414 for i in 0..3 {
415 com[i] += child.center[i] * v;
416 }
417 }
418 for i in 0..3 {
419 com[i] /= total_vol;
420 }
421 com
422 }
423 pub fn contains_point(&self, p: [f64; 3]) -> bool {
425 for child in &self.children {
426 if Self::child_contains(child, p) {
427 return true;
428 }
429 }
430 false
431 }
432 fn child_contains(child: &CompoundChild, p: [f64; 3]) -> bool {
434 let dx = p[0] - child.center[0];
435 let dy = p[1] - child.center[1];
436 let dz = p[2] - child.center[2];
437 match child.shape_kind {
438 ChildShapeKind::Sphere { radius } => dx * dx + dy * dy + dz * dz <= radius * radius,
439 ChildShapeKind::Box { half_extents } => {
440 dx.abs() <= half_extents[0]
441 && dy.abs() <= half_extents[1]
442 && dz.abs() <= half_extents[2]
443 }
444 ChildShapeKind::Capsule {
445 radius,
446 half_height,
447 } => {
448 let clamped_y = dy.clamp(-half_height, half_height);
449 let ry = dy - clamped_y;
450 dx * dx + ry * ry + dz * dz <= radius * radius
451 }
452 }
453 }
454 pub fn ray_cast(
457 &self,
458 origin: [f64; 3],
459 dir: [f64; 3],
460 max_toi: f64,
461 ) -> Option<(f64, [f64; 3], usize)> {
462 let mut best: Option<(f64, [f64; 3], usize)> = None;
463 for (idx, child) in self.children.iter().enumerate() {
464 if let Some((t, n)) = Self::ray_cast_child(child, origin, dir, max_toi)
465 && best.as_ref().is_none_or(|(bt, _, _)| t < *bt)
466 {
467 best = Some((t, n, idx));
468 }
469 }
470 best
471 }
472 fn ray_cast_child(
474 child: &CompoundChild,
475 origin: [f64; 3],
476 dir: [f64; 3],
477 max_toi: f64,
478 ) -> Option<(f64, [f64; 3])> {
479 let lo = [
480 origin[0] - child.center[0],
481 origin[1] - child.center[1],
482 origin[2] - child.center[2],
483 ];
484 match child.shape_kind {
485 ChildShapeKind::Sphere { radius } => ray_sphere(lo, dir, radius, max_toi),
486 ChildShapeKind::Box { half_extents } => ray_box(lo, dir, half_extents, max_toi),
487 ChildShapeKind::Capsule {
488 radius,
489 half_height,
490 } => ray_capsule(lo, dir, radius, half_height, max_toi),
491 }
492 }
493}
494impl CompoundShape {
495 pub fn inertia_tensor(&self, density: f64) -> [[f64; 3]; 3] {
499 let com = self.center_of_mass();
500 let mut i_xx = 0.0f64;
501 let mut i_yy = 0.0f64;
502 let mut i_zz = 0.0f64;
503 let mut i_xy = 0.0f64;
504 let mut i_xz = 0.0f64;
505 let mut i_yz = 0.0f64;
506 for child in &self.children {
507 let vol = Self::child_volume(&child.shape_kind);
508 let m = density * vol;
509 let (lxx, lyy, lzz) = Self::child_local_inertia(&child.shape_kind, m);
510 let r = [
511 child.center[0] - com[0],
512 child.center[1] - com[1],
513 child.center[2] - com[2],
514 ];
515 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
516 i_xx += lxx + m * (r2 - r[0] * r[0]);
517 i_yy += lyy + m * (r2 - r[1] * r[1]);
518 i_zz += lzz + m * (r2 - r[2] * r[2]);
519 i_xy -= m * r[0] * r[1];
520 i_xz -= m * r[0] * r[2];
521 i_yz -= m * r[1] * r[2];
522 }
523 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
524 }
525 fn child_local_inertia(kind: &ChildShapeKind, mass: f64) -> (f64, f64, f64) {
527 match kind {
528 ChildShapeKind::Sphere { radius } => {
529 let i = 2.0 / 5.0 * mass * radius * radius;
530 (i, i, i)
531 }
532 ChildShapeKind::Box { half_extents } => {
533 let [hx, hy, hz] = *half_extents;
534 let i_xx = mass / 3.0 * (hy * hy + hz * hz);
535 let i_yy = mass / 3.0 * (hx * hx + hz * hz);
536 let i_zz = mass / 3.0 * (hx * hx + hy * hy);
537 (i_xx, i_yy, i_zz)
538 }
539 ChildShapeKind::Capsule {
540 radius,
541 half_height,
542 } => {
543 let r = radius;
544 let h = half_height * 2.0;
545 let m_cyl = mass * std::f64::consts::PI * r * r * h
546 / (std::f64::consts::PI * r * r * h
547 + (4.0 / 3.0) * std::f64::consts::PI * r * r * r);
548 let m_hemi = (mass - m_cyl) / 2.0;
549 let i_cyl_xx = m_cyl * (3.0 * r * r + h * h) / 12.0;
550 let i_hemi_xx = m_hemi * (2.0 * r * r / 5.0 + (3.0 * half_height / 8.0).powi(2));
551 let i_xx = i_cyl_xx + 2.0 * i_hemi_xx;
552 let i_yy = m_cyl * r * r / 2.0 + 2.0 * m_hemi * 2.0 * r * r / 5.0;
553 (i_xx, i_yy, i_xx)
554 }
555 }
556 }
557 pub fn bounding_sphere(&self) -> ([f64; 3], f64) {
559 let com = self.center_of_mass();
560 let mut max_r = 0.0f64;
561 for child in &self.children {
562 let child_r = self.child_bounding_radius(child);
563 let dist_to_com = {
564 let dx = child.center[0] - com[0];
565 let dy = child.center[1] - com[1];
566 let dz = child.center[2] - com[2];
567 (dx * dx + dy * dy + dz * dz).sqrt()
568 };
569 let r = dist_to_com + child_r;
570 if r > max_r {
571 max_r = r;
572 }
573 }
574 (com, max_r)
575 }
576 fn child_bounding_radius(&self, child: &CompoundChild) -> f64 {
577 match child.shape_kind {
578 ChildShapeKind::Sphere { radius } => radius,
579 ChildShapeKind::Box { half_extents } => {
580 (half_extents[0].powi(2) + half_extents[1].powi(2) + half_extents[2].powi(2)).sqrt()
581 }
582 ChildShapeKind::Capsule {
583 radius,
584 half_height,
585 } => half_height + radius,
586 }
587 }
588 pub fn scale(&mut self, factor: f64) {
590 for child in &mut self.children {
591 child.center[0] *= factor;
592 child.center[1] *= factor;
593 child.center[2] *= factor;
594 match &mut child.shape_kind {
595 ChildShapeKind::Sphere { radius } => *radius *= factor,
596 ChildShapeKind::Box { half_extents } => {
597 half_extents[0] *= factor;
598 half_extents[1] *= factor;
599 half_extents[2] *= factor;
600 }
601 ChildShapeKind::Capsule {
602 radius,
603 half_height,
604 } => {
605 *radius *= factor;
606 *half_height *= factor;
607 }
608 }
609 }
610 }
611 pub fn translate(&mut self, offset: [f64; 3]) {
613 for child in &mut self.children {
614 child.center[0] += offset[0];
615 child.center[1] += offset[1];
616 child.center[2] += offset[2];
617 }
618 }
619 pub fn merge_with(&self, other: &CompoundShape) -> CompoundShape {
621 let mut result = self.clone();
622 result.children.extend(other.children.iter().cloned());
623 result
624 }
625 pub fn overlaps_sphere(&self, center: [f64; 3], radius: f64) -> bool {
629 for child in &self.children {
630 let dx = child.center[0] - center[0];
631 let dy = child.center[1] - center[1];
632 let dz = child.center[2] - center[2];
633 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
634 let child_r = self.child_bounding_radius(child);
635 if dist < child_r + radius {
636 return true;
637 }
638 }
639 false
640 }
641 pub fn ray_cast_all(
643 &self,
644 origin: [f64; 3],
645 dir: [f64; 3],
646 max_toi: f64,
647 ) -> Vec<(f64, [f64; 3], usize)> {
648 let mut hits: Vec<(f64, [f64; 3], usize)> = Vec::new();
649 for (idx, child) in self.children.iter().enumerate() {
650 if let Some((t, n)) = Self::ray_cast_child(child, origin, dir, max_toi) {
651 hits.push((t, n, idx));
652 }
653 }
654 hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
655 hits
656 }
657}
658#[allow(dead_code)]
659impl CompoundShape {
660 pub fn merged_aabb(&self) -> ([f64; 3], [f64; 3]) {
662 self.aabb()
663 }
664 pub fn raycast(
668 &self,
669 ray_origin: [f64; 3],
670 ray_dir: [f64; 3],
671 max_t: f64,
672 ) -> Option<(f64, usize)> {
673 self.ray_cast(ray_origin, ray_dir, max_t)
674 .map(|(t, _n, idx)| (t, idx))
675 }
676 pub fn volume(&self) -> f64 {
678 self.total_volume()
679 }
680 pub fn center_of_mass_weighted(&self, masses: &[f64]) -> [f64; 3] {
685 let total: f64 = masses.iter().copied().take(self.children.len()).sum();
686 if total < 1e-30 {
687 return [0.0; 3];
688 }
689 let mut com = [0.0f64; 3];
690 for (i, child) in self.children.iter().enumerate() {
691 let m = if i < masses.len() { masses[i] } else { 0.0 };
692 for k in 0..3 {
693 com[k] += m * child.center[k];
694 }
695 }
696 for k in 0..3 {
697 com[k] /= total;
698 }
699 com
700 }
701 pub fn inertia_tensor_from_masses(&self, masses: &[f64]) -> [[f64; 3]; 3] {
705 let com = self.center_of_mass_weighted(masses);
706 let mut i_xx = 0.0f64;
707 let mut i_yy = 0.0f64;
708 let mut i_zz = 0.0f64;
709 let mut i_xy = 0.0f64;
710 let mut i_xz = 0.0f64;
711 let mut i_yz = 0.0f64;
712 for (idx, child) in self.children.iter().enumerate() {
713 let m = if idx < masses.len() { masses[idx] } else { 0.0 };
714 let (lxx, lyy, lzz) = Self::child_local_inertia(&child.shape_kind, m);
715 let r = [
716 child.center[0] - com[0],
717 child.center[1] - com[1],
718 child.center[2] - com[2],
719 ];
720 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
721 i_xx += lxx + m * (r2 - r[0] * r[0]);
722 i_yy += lyy + m * (r2 - r[1] * r[1]);
723 i_zz += lzz + m * (r2 - r[2] * r[2]);
724 i_xy -= m * r[0] * r[1];
725 i_xz -= m * r[0] * r[2];
726 i_yz -= m * r[1] * r[2];
727 }
728 [[i_xx, i_xy, i_xz], [i_xy, i_yy, i_yz], [i_xz, i_yz, i_zz]]
729 }
730 pub fn closest_point(&self, p: [f64; 3]) -> ([f64; 3], usize) {
734 let mut best_dist = f64::INFINITY;
735 let mut best_pt = p;
736 let mut best_idx = 0usize;
737 for (i, child) in self.children.iter().enumerate() {
738 let cp = Self::child_closest_point(child, p);
739 let dx = cp[0] - p[0];
740 let dy = cp[1] - p[1];
741 let dz = cp[2] - p[2];
742 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
743 if dist < best_dist {
744 best_dist = dist;
745 best_pt = cp;
746 best_idx = i;
747 }
748 }
749 (best_pt, best_idx)
750 }
751 fn child_closest_point(child: &CompoundChild, p: [f64; 3]) -> [f64; 3] {
752 let c = child.center;
753 match child.shape_kind {
754 ChildShapeKind::Sphere { radius } => {
755 let dx = p[0] - c[0];
756 let dy = p[1] - c[1];
757 let dz = p[2] - c[2];
758 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
759 if dist < 1e-30 {
760 [c[0] + radius, c[1], c[2]]
761 } else {
762 let scale = radius / dist;
763 [c[0] + dx * scale, c[1] + dy * scale, c[2] + dz * scale]
764 }
765 }
766 ChildShapeKind::Box { half_extents } => {
767 let clamped = [
768 (p[0] - c[0]).clamp(-half_extents[0], half_extents[0]) + c[0],
769 (p[1] - c[1]).clamp(-half_extents[1], half_extents[1]) + c[1],
770 (p[2] - c[2]).clamp(-half_extents[2], half_extents[2]) + c[2],
771 ];
772 let inside = (0..3).all(|i| {
773 let he = [half_extents[0], half_extents[1], half_extents[2]][i];
774 let d = [p[0] - c[0], p[1] - c[1], p[2] - c[2]][i].abs();
775 d <= he
776 });
777 if inside {
778 let dx_neg = [half_extents[0], half_extents[1], half_extents[2]][0]
779 - (p[0] - c[0]).abs();
780 let dy_neg = [half_extents[0], half_extents[1], half_extents[2]][1]
781 - (p[1] - c[1]).abs();
782 let dz_neg = [half_extents[0], half_extents[1], half_extents[2]][2]
783 - (p[2] - c[2]).abs();
784 if dx_neg <= dy_neg && dx_neg <= dz_neg {
785 let sx = if p[0] >= c[0] { 1.0 } else { -1.0 };
786 [c[0] + half_extents[0] * sx, p[1], p[2]]
787 } else if dy_neg <= dx_neg && dy_neg <= dz_neg {
788 let sy = if p[1] >= c[1] { 1.0 } else { -1.0 };
789 [p[0], c[1] + half_extents[1] * sy, p[2]]
790 } else {
791 let sz = if p[2] >= c[2] { 1.0 } else { -1.0 };
792 [p[0], p[1], c[2] + half_extents[2] * sz]
793 }
794 } else {
795 clamped
796 }
797 }
798 ChildShapeKind::Capsule {
799 radius,
800 half_height,
801 } => {
802 let dy = p[1] - c[1];
803 let clamped_y = dy.clamp(-half_height, half_height);
804 let axis_pt = [c[0], c[1] + clamped_y, c[2]];
805 let dx = p[0] - axis_pt[0];
806 let dpz = p[2] - axis_pt[2];
807 let dist_xz = (dx * dx + dpz * dpz).sqrt();
808 if dist_xz < 1e-30 {
809 [axis_pt[0] + radius, axis_pt[1], axis_pt[2]]
810 } else {
811 let scale = radius / dist_xz;
812 [
813 axis_pt[0] + dx * scale,
814 axis_pt[1],
815 axis_pt[2] + dpz * scale,
816 ]
817 }
818 }
819 }
820 }
821}
822#[allow(dead_code)]
823impl CompoundShape {
824 pub fn remove_child(&mut self, index: usize) {
826 self.children.remove(index);
827 }
828 pub fn swap_remove_child(&mut self, index: usize) {
833 self.children.swap_remove(index);
834 }
835 pub fn replace_with_sphere(&mut self, index: usize, center: [f64; 3], radius: f64) {
837 self.children[index] = CompoundChild {
838 center,
839 shape_kind: ChildShapeKind::Sphere { radius },
840 };
841 }
842 pub fn replace_with_box(&mut self, index: usize, center: [f64; 3], half_extents: [f64; 3]) {
844 self.children[index] = CompoundChild {
845 center,
846 shape_kind: ChildShapeKind::Box { half_extents },
847 };
848 }
849 pub fn is_empty(&self) -> bool {
851 self.children.is_empty()
852 }
853 pub fn clear(&mut self) {
855 self.children.clear();
856 }
857 pub fn closest_point_with_dist2(&self, p: [f64; 3]) -> ([f64; 3], f64, usize) {
862 let mut best_dist2 = f64::INFINITY;
863 let mut best_pt = p;
864 let mut best_idx = 0usize;
865 for (i, child) in self.children.iter().enumerate() {
866 let cp = Self::child_closest_point(child, p);
867 let dx = cp[0] - p[0];
868 let dy = cp[1] - p[1];
869 let dz = cp[2] - p[2];
870 let d2 = dx * dx + dy * dy + dz * dz;
871 if d2 < best_dist2 {
872 best_dist2 = d2;
873 best_pt = cp;
874 best_idx = i;
875 }
876 }
877 (best_pt, best_dist2, best_idx)
878 }
879 pub fn broad_phase_pairs(&self, other: &CompoundShape) -> Vec<(usize, usize)> {
884 let mut pairs = Vec::new();
885 for (i, ci) in self.children.iter().enumerate() {
886 let ri = self.child_bounding_radius(ci);
887 for (j, cj) in other.children.iter().enumerate() {
888 let rj = other.child_bounding_radius(cj);
889 let dx = ci.center[0] - cj.center[0];
890 let dy = ci.center[1] - cj.center[1];
891 let dz = ci.center[2] - cj.center[2];
892 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
893 if dist < ri + rj {
894 pairs.push((i, j));
895 }
896 }
897 }
898 pairs
899 }
900 pub fn overlaps_compound(&self, other: &CompoundShape) -> bool {
902 !self.broad_phase_pairs(other).is_empty()
903 }
904 pub fn centroid_with_densities(&self, densities: &[f64]) -> [f64; 3] {
910 let mut total_mass = 0.0f64;
911 let mut com = [0.0f64; 3];
912 for (i, child) in self.children.iter().enumerate() {
913 let rho = if i < densities.len() {
914 densities[i]
915 } else {
916 1.0
917 };
918 let vol = Self::child_volume(&child.shape_kind);
919 let m = rho * vol;
920 total_mass += m;
921 for k in 0..3 {
922 com[k] += m * child.center[k];
923 }
924 }
925 if total_mass < 1e-30 {
926 return [0.0; 3];
927 }
928 for k in 0..3 {
929 com[k] /= total_mass;
930 }
931 com
932 }
933 pub fn penetration_depth_sphere(&self, center: [f64; 3], radius: f64) -> Option<(f64, usize)> {
942 let mut best: Option<(f64, usize)> = None;
943 for (i, child) in self.children.iter().enumerate() {
944 let signed = self.signed_distance_child(child, center, radius);
945 if signed < 0.0 && best.as_ref().is_none_or(|(bd, _)| signed < *bd) {
946 best = Some((signed, i));
947 }
948 }
949 best
950 }
951 fn signed_distance_child(
955 &self,
956 child: &CompoundChild,
957 sphere_center: [f64; 3],
958 sphere_radius: f64,
959 ) -> f64 {
960 let cp = Self::child_closest_point(child, sphere_center);
961 let dx = cp[0] - sphere_center[0];
962 let dy = cp[1] - sphere_center[1];
963 let dz = cp[2] - sphere_center[2];
964 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
965 dist - sphere_radius
966 }
967 pub fn child_masses(&self, density: f64) -> Vec<f64> {
969 self.children
970 .iter()
971 .map(|c| density * Self::child_volume(&c.shape_kind))
972 .collect()
973 }
974 pub fn total_mass(&self, density: f64) -> f64 {
976 density * self.total_volume()
977 }
978 pub fn child_aabb_public(child: &CompoundChild) -> ([f64; 3], [f64; 3]) {
980 Self::child_aabb(child)
981 }
982 pub fn expanded_aabb(&self, margin: f64) -> ([f64; 3], [f64; 3]) {
984 let (mn, mx) = self.aabb();
985 (
986 [mn[0] - margin, mn[1] - margin, mn[2] - margin],
987 [mx[0] + margin, mx[1] + margin, mx[2] + margin],
988 )
989 }
990 pub fn sphere_overlaps_aabb(&self, center: [f64; 3], radius: f64) -> bool {
992 if self.children.is_empty() {
993 return false;
994 }
995 let (mn, mx) = self.aabb();
996 let cx = center[0].clamp(mn[0], mx[0]);
997 let cy = center[1].clamp(mn[1], mx[1]);
998 let cz = center[2].clamp(mn[2], mx[2]);
999 let dx = cx - center[0];
1000 let dy = cy - center[1];
1001 let dz = cz - center[2];
1002 dx * dx + dy * dy + dz * dz <= radius * radius
1003 }
1004}