parry2d/shape/
compound.rs

1//!
2//! Shape composed from the union of primitives.
3//!
4
5use 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/// A compound shape with an aabb bounding volume.
17///
18/// A compound shape is a shape composed of the union of several simpler shape. This is
19/// the main way of creating a concave shape from convex parts. Each parts can have its own
20/// delta transformation to shift or rotate it with regard to the other shapes.
21#[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    /// Builds a new compound shape.
32    ///
33    /// Panics if the input vector is empty, of if some of the provided shapes
34    /// are also composite shapes (nested composite shapes are not allowed).
35    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        // NOTE: we apply no dilation factor because we won't
57        // update this tree dynamically.
58        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    /// Create a compound shape from the `TriMesh`. This involves merging adjacent triangles into convex
70    /// polygons using the Hertel-Mehlhorn algorithm.
71    ///
72    /// Can fail and return `None` if any of the created shapes has close to zero or zero surface area.
73    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    /// The shapes of this compound shape.
94    #[inline]
95    pub fn shapes(&self) -> &[(Isometry<Real>, SharedShape)] {
96        &self.shapes[..]
97    }
98
99    /// The [`Aabb`] of this compound in its local-space.
100    #[inline]
101    pub fn local_aabb(&self) -> &Aabb {
102        &self.aabb
103    }
104
105    /// The bounding-sphere of this compound in its local-space.
106    #[inline]
107    pub fn local_bounding_sphere(&self) -> BoundingSphere {
108        self.aabb.bounding_sphere()
109    }
110
111    /// The shapes Aabbs.
112    #[inline]
113    pub fn aabbs(&self) -> &[Aabb] {
114        &self.aabbs[..]
115    }
116
117    /// The acceleration structure used by this compound shape.
118    #[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}