oxiphysics_geometry/compound/
compound_traits.rs1use crate::shape::{RayHit, Shape};
12use oxiphysics_core::Aabb;
13use oxiphysics_core::math::{Mat3, Real, Vec3};
14
15use super::types::Compound;
16
17impl Shape for Compound {
18 fn bounding_box(&self) -> Aabb {
19 if self.children.is_empty() {
20 return Aabb::new(Vec3::zeros(), Vec3::zeros());
21 }
22 let mut result: Option<Aabb> = None;
23 for (transform, shape) in &self.children {
24 let local_aabb = shape.bounding_box();
25 let corners = [
26 Vec3::new(local_aabb.min.x, local_aabb.min.y, local_aabb.min.z),
27 Vec3::new(local_aabb.max.x, local_aabb.min.y, local_aabb.min.z),
28 Vec3::new(local_aabb.min.x, local_aabb.max.y, local_aabb.min.z),
29 Vec3::new(local_aabb.max.x, local_aabb.max.y, local_aabb.min.z),
30 Vec3::new(local_aabb.min.x, local_aabb.min.y, local_aabb.max.z),
31 Vec3::new(local_aabb.max.x, local_aabb.min.y, local_aabb.max.z),
32 Vec3::new(local_aabb.min.x, local_aabb.max.y, local_aabb.max.z),
33 Vec3::new(local_aabb.max.x, local_aabb.max.y, local_aabb.max.z),
34 ];
35 let mut min = transform.transform_point(&corners[0]);
36 let mut max = min;
37 for corner in &corners[1..] {
38 let p = transform.transform_point(corner);
39 min = min.inf(&p);
40 max = max.sup(&p);
41 }
42 let child_aabb = Aabb::new(min, max);
43 result = Some(match result {
44 Some(r) => r.merge(&child_aabb),
45 None => child_aabb,
46 });
47 }
48 result.unwrap_or_else(|| Aabb::new(Vec3::zeros(), Vec3::zeros()))
49 }
50 fn support_point(&self, direction: &Vec3) -> Vec3 {
51 let mut best_dot = Real::NEG_INFINITY;
52 let mut best_point = Vec3::zeros();
53 for (transform, shape) in &self.children {
54 let local_dir = transform.inverse().transform_vector(direction);
55 let local_support = shape.support_point(&local_dir);
56 let world_support = transform.transform_point(&local_support);
57 let d = world_support.dot(direction);
58 if d > best_dot {
59 best_dot = d;
60 best_point = world_support;
61 }
62 }
63 best_point
64 }
65 fn volume(&self) -> Real {
66 self.children.iter().map(|(_, s)| s.volume()).sum()
67 }
68 fn center_of_mass(&self) -> Vec3 {
69 let total_vol: Real = self.children.iter().map(|(_, s)| s.volume()).sum();
70 if total_vol < 1e-12 {
71 return Vec3::zeros();
72 }
73 let weighted: Vec3 = self
74 .children
75 .iter()
76 .map(|(t, s)| {
77 let local_com = s.center_of_mass();
78 let world_com = t.transform_point(&local_com);
79 world_com * s.volume()
80 })
81 .sum();
82 weighted / total_vol
83 }
84 fn inertia_tensor(&self, mass: Real) -> Mat3 {
85 let total_vol: Real = self.children.iter().map(|(_, s)| s.volume()).sum();
86 if total_vol < 1e-12 {
87 return Mat3::zeros();
88 }
89 let com = self.center_of_mass();
90 let mut total = Mat3::zeros();
91 for (transform, shape) in &self.children {
92 let child_mass = mass * shape.volume() / total_vol;
93 let child_inertia = shape.inertia_tensor(child_mass);
94 let child_com = transform.transform_point(&shape.center_of_mass());
95 let r = child_com - com;
96 let r2 = r.dot(&r);
97 let parallel = Mat3::identity() * r2 - r * r.transpose();
98 total += child_inertia + parallel * child_mass;
99 }
100 total
101 }
102 fn ray_cast(&self, ray_origin: &Vec3, ray_direction: &Vec3, max_toi: Real) -> Option<RayHit> {
103 let mut best: Option<RayHit> = None;
104 for (transform, shape) in &self.children {
105 let inv = transform.inverse();
106 let local_origin = inv.transform_point(ray_origin);
107 let local_dir = inv.transform_vector(ray_direction);
108 if let Some(hit) = shape.ray_cast(&local_origin, &local_dir, max_toi)
109 && best.as_ref().is_none_or(|b| hit.toi < b.toi)
110 {
111 let world_point = transform.transform_point(&hit.point);
112 let world_normal = transform.transform_vector(&hit.normal).normalize();
113 best = Some(RayHit {
114 point: world_point,
115 normal: world_normal,
116 toi: hit.toi,
117 });
118 }
119 }
120 best
121 }
122}