parry2d/shape/
compound.rs1use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume};
6use crate::math::{Isometry, Real};
7use crate::partitioning::{Bvh, BvhBuildStrategy};
8use crate::query::details::NormalConstraints;
9use crate::shape::{CompositeShape, Shape, SharedShape, TypedCompositeShape};
10#[cfg(feature = "dim2")]
11use crate::shape::{ConvexPolygon, TriMesh, Triangle};
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 bvh: Bvh,
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, bv));
50
51 if shape.as_composite_shape().is_some() {
52 panic!("Nested composite shapes are not allowed.");
53 }
54 }
55
56 let bvh = Bvh::from_iter(BvhBuildStrategy::Binned, leaves);
59
60 Compound {
61 shapes,
62 bvh,
63 aabbs,
64 aabb,
65 }
66 }
67
68 #[cfg(feature = "dim2")]
69 pub fn decompose_trimesh(trimesh: &TriMesh) -> Option<Self> {
74 let polygons = hertel_mehlhorn(trimesh.vertices(), trimesh.indices());
75 let shapes: Option<Vec<_>> = polygons
76 .into_iter()
77 .map(|points| {
78 match points.len() {
79 3 => {
80 let triangle = Triangle::new(points[0], points[1], points[2]);
81 Some(SharedShape::new(triangle))
82 }
83 _ => ConvexPolygon::from_convex_polyline(points).map(SharedShape::new),
84 }
85 .map(|shape| (Isometry::identity(), shape))
86 })
87 .collect();
88 Some(Self::new(shapes?))
89 }
90}
91
92impl Compound {
93 #[inline]
95 pub fn shapes(&self) -> &[(Isometry<Real>, SharedShape)] {
96 &self.shapes[..]
97 }
98
99 #[inline]
101 pub fn local_aabb(&self) -> &Aabb {
102 &self.aabb
103 }
104
105 #[inline]
107 pub fn local_bounding_sphere(&self) -> BoundingSphere {
108 self.aabb.bounding_sphere()
109 }
110
111 #[inline]
113 pub fn aabbs(&self) -> &[Aabb] {
114 &self.aabbs[..]
115 }
116
117 #[inline]
119 pub fn bvh(&self) -> &Bvh {
120 &self.bvh
121 }
122}
123
124impl CompositeShape for Compound {
125 #[inline]
126 fn map_part_at(
127 &self,
128 shape_id: u32,
129 f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
130 ) {
131 if let Some(shape) = self.shapes.get(shape_id as usize) {
132 f(Some(&shape.0), &*shape.1, None)
133 }
134 }
135
136 #[inline]
137 fn bvh(&self) -> &Bvh {
138 &self.bvh
139 }
140}
141
142impl TypedCompositeShape for Compound {
143 type PartShape = dyn Shape;
144 type PartNormalConstraints = ();
145
146 #[inline(always)]
147 fn map_typed_part_at<T>(
148 &self,
149 i: u32,
150 mut f: impl FnMut(
151 Option<&Isometry<Real>>,
152 &Self::PartShape,
153 Option<&Self::PartNormalConstraints>,
154 ) -> T,
155 ) -> Option<T> {
156 let (part_pos, part) = &self.shapes[i as usize];
157 Some(f(Some(part_pos), &**part, None))
158 }
159
160 #[inline(always)]
161 fn map_untyped_part_at<T>(
162 &self,
163 i: u32,
164 mut f: impl FnMut(
165 Option<&Isometry<Real>>,
166 &Self::PartShape,
167 Option<&dyn NormalConstraints>,
168 ) -> T,
169 ) -> Option<T> {
170 let (part_pos, part) = &self.shapes[i as usize];
171 Some(f(Some(part_pos), &**part, None))
172 }
173}