parry3d_f64/shape/
compound.rs1use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume};
6use crate::math::{Isometry, Real};
7use crate::partitioning::Qbvh;
8use crate::query::details::NormalConstraints;
9#[cfg(feature = "dim2")]
10use crate::shape::{ConvexPolygon, TriMesh, Triangle};
11use crate::shape::{Shape, SharedShape, SimdCompositeShape, TypedSimdCompositeShape};
12#[cfg(feature = "dim2")]
13use crate::transformation::hertel_mehlhorn;
14use alloc::vec::Vec;
15
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[derive(Clone, Debug)]
23pub struct Compound {
24 shapes: Vec<(Isometry<Real>, SharedShape)>,
25 qbvh: Qbvh<u32>,
26 aabbs: Vec<Aabb>,
27 aabb: Aabb,
28}
29
30impl Compound {
31 pub fn new(shapes: Vec<(Isometry<Real>, SharedShape)>) -> Compound {
36 assert!(
37 !shapes.is_empty(),
38 "A compound shape must contain at least one shape."
39 );
40 let mut aabbs = Vec::new();
41 let mut leaves = Vec::new();
42 let mut aabb = Aabb::new_invalid();
43
44 for (i, (delta, shape)) in shapes.iter().enumerate() {
45 let bv = shape.compute_aabb(delta);
46
47 aabb.merge(&bv);
48 aabbs.push(bv);
49 leaves.push((i as u32, bv));
50
51 if shape.as_composite_shape().is_some() {
52 panic!("Nested composite shapes are not allowed.");
53 }
54 }
55
56 let mut qbvh = Qbvh::new();
57 qbvh.clear_and_rebuild(leaves.into_iter(), 0.0);
60
61 Compound {
62 shapes,
63 qbvh,
64 aabbs,
65 aabb,
66 }
67 }
68
69 #[cfg(feature = "dim2")]
70 pub fn decompose_trimesh(trimesh: &TriMesh) -> Option<Self> {
75 let polygons = hertel_mehlhorn(trimesh.vertices(), trimesh.indices());
76 let shapes: Option<Vec<_>> = polygons
77 .into_iter()
78 .map(|points| {
79 match points.len() {
80 3 => {
81 let triangle = Triangle::new(points[0], points[1], points[2]);
82 Some(SharedShape::new(triangle))
83 }
84 _ => ConvexPolygon::from_convex_polyline(points).map(SharedShape::new),
85 }
86 .map(|shape| (Isometry::identity(), shape))
87 })
88 .collect();
89 Some(Self::new(shapes?))
90 }
91}
92
93impl Compound {
94 #[inline]
96 pub fn shapes(&self) -> &[(Isometry<Real>, SharedShape)] {
97 &self.shapes[..]
98 }
99
100 #[inline]
102 pub fn local_aabb(&self) -> &Aabb {
103 &self.aabb
104 }
105
106 #[inline]
108 pub fn local_bounding_sphere(&self) -> BoundingSphere {
109 self.aabb.bounding_sphere()
110 }
111
112 #[inline]
114 pub fn aabbs(&self) -> &[Aabb] {
115 &self.aabbs[..]
116 }
117
118 #[inline]
120 pub fn qbvh(&self) -> &Qbvh<u32> {
121 &self.qbvh
122 }
123}
124
125impl SimdCompositeShape for Compound {
126 #[inline]
127 fn map_part_at(
128 &self,
129 shape_id: u32,
130 f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
131 ) {
132 if let Some(shape) = self.shapes.get(shape_id as usize) {
133 f(Some(&shape.0), &*shape.1, None)
134 }
135 }
136
137 #[inline]
138 fn qbvh(&self) -> &Qbvh<u32> {
139 &self.qbvh
140 }
141}
142
143impl TypedSimdCompositeShape for Compound {
144 type PartShape = dyn Shape;
145 type PartNormalConstraints = ();
146 type PartId = u32;
147
148 #[inline(always)]
149 fn map_typed_part_at(
150 &self,
151 i: u32,
152 mut f: impl FnMut(
153 Option<&Isometry<Real>>,
154 &Self::PartShape,
155 Option<&Self::PartNormalConstraints>,
156 ),
157 ) {
158 if let Some((part_pos, part)) = self.shapes.get(i as usize) {
159 f(Some(part_pos), &**part, None)
160 }
161 }
162
163 #[inline(always)]
164 fn map_untyped_part_at(
165 &self,
166 i: u32,
167 mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape, Option<&dyn NormalConstraints>),
168 ) {
169 if let Some((part_pos, part)) = self.shapes.get(i as usize) {
170 f(Some(part_pos), &**part, None)
171 }
172 }
173
174 #[inline]
175 fn typed_qbvh(&self) -> &Qbvh<u32> {
176 &self.qbvh
177 }
178}