parry3d_f64/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::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/// 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    qbvh: Qbvh<u32>,
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 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        // NOTE: we apply no dilation factor because we won't
58        // update this tree dynamically.
59        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    /// Create a compound shape from the `TriMesh`. This involves merging adjacent triangles into convex
71    /// polygons using the Hertel-Mehlhorn algorithm.
72    ///
73    /// Can fail and return `None` if any of the created shapes has close to zero or zero surface area.
74    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    /// The shapes of this compound shape.
95    #[inline]
96    pub fn shapes(&self) -> &[(Isometry<Real>, SharedShape)] {
97        &self.shapes[..]
98    }
99
100    /// The [`Aabb`] of this compound in its local-space.
101    #[inline]
102    pub fn local_aabb(&self) -> &Aabb {
103        &self.aabb
104    }
105
106    /// The bounding-sphere of this compound in its local-space.
107    #[inline]
108    pub fn local_bounding_sphere(&self) -> BoundingSphere {
109        self.aabb.bounding_sphere()
110    }
111
112    /// The shapes Aabbs.
113    #[inline]
114    pub fn aabbs(&self) -> &[Aabb] {
115        &self.aabbs[..]
116    }
117
118    /// The acceleration structure used by this compound shape.
119    #[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}