parry3d_f64/shape/
shared_shape.rs

1use super::TriMeshBuilderError;
2use crate::math::{Isometry, Point, Real, Vector, DIM};
3use crate::shape::voxels::VoxelPrimitiveGeometry;
4#[cfg(feature = "dim2")]
5use crate::shape::ConvexPolygon;
6#[cfg(feature = "serde-serialize")]
7use crate::shape::DeserializableTypedShape;
8#[cfg(feature = "dim3")]
9use crate::shape::HeightFieldFlags;
10use crate::shape::{
11    Ball, Capsule, Compound, Cuboid, HalfSpace, HeightField, Polyline, RoundShape, Segment, Shape,
12    TriMesh, TriMeshFlags, Triangle, TypedShape, Voxels,
13};
14#[cfg(feature = "dim3")]
15use crate::shape::{Cone, ConvexPolyhedron, Cylinder};
16use crate::transformation::vhacd::{VHACDParameters, VHACD};
17use crate::transformation::voxelization::{FillMode, VoxelSet};
18use alloc::sync::Arc;
19use alloc::{vec, vec::Vec};
20use core::fmt;
21use core::ops::Deref;
22use na::Unit;
23
24/// The shape of a collider.
25#[derive(Clone)]
26pub struct SharedShape(pub Arc<dyn Shape>);
27
28impl Deref for SharedShape {
29    type Target = dyn Shape;
30    fn deref(&self) -> &dyn Shape {
31        &*self.0
32    }
33}
34
35impl AsRef<dyn Shape> for SharedShape {
36    fn as_ref(&self) -> &dyn Shape {
37        &*self.0
38    }
39}
40
41impl fmt::Debug for SharedShape {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        let typed_shape: TypedShape = (*self.0).as_typed_shape();
44        write!(f, "SharedShape ( Arc<{:?}> )", typed_shape)
45    }
46}
47
48impl SharedShape {
49    /// Wraps the given shape as a shared shape.
50    pub fn new(shape: impl Shape) -> Self {
51        Self(Arc::new(shape))
52    }
53
54    /// If this shape is shared, then the content of `self` is cloned into a unique instance,
55    /// and a mutable reference to that instance is returned.
56    pub fn make_mut(&mut self) -> &mut dyn Shape {
57        if Arc::get_mut(&mut self.0).is_none() {
58            let unique_self = self.0.clone_dyn();
59            self.0 = unique_self.into();
60        }
61        Arc::get_mut(&mut self.0).unwrap()
62    }
63
64    /// Initialize a compound shape defined by its subshapes.
65    pub fn compound(shapes: Vec<(Isometry<Real>, SharedShape)>) -> Self {
66        let raw_shapes = shapes.into_iter().map(|s| (s.0, s.1)).collect();
67        let compound = Compound::new(raw_shapes);
68        SharedShape(Arc::new(compound))
69    }
70
71    /// Initialize a ball shape defined by its radius.
72    pub fn ball(radius: Real) -> Self {
73        SharedShape(Arc::new(Ball::new(radius)))
74    }
75
76    /// Initialize a plane shape defined by its outward normal.
77    pub fn halfspace(outward_normal: Unit<Vector<Real>>) -> Self {
78        SharedShape(Arc::new(HalfSpace::new(outward_normal)))
79    }
80
81    /// Initialize a cylindrical shape defined by its half-height
82    /// (along along the y axis) and its radius.
83    #[cfg(feature = "dim3")]
84    pub fn cylinder(half_height: Real, radius: Real) -> Self {
85        SharedShape(Arc::new(Cylinder::new(half_height, radius)))
86    }
87
88    /// Initialize a rounded cylindrical shape defined by its half-height
89    /// (along along the y axis), its radius, and its roundedness (the
90    /// radius of the sphere used for dilating the cylinder).
91    #[cfg(feature = "dim3")]
92    pub fn round_cylinder(half_height: Real, radius: Real, border_radius: Real) -> Self {
93        SharedShape(Arc::new(RoundShape {
94            inner_shape: Cylinder::new(half_height, radius),
95            border_radius,
96        }))
97    }
98
99    /// Initialize a rounded cone shape defined by its half-height
100    /// (along along the y axis), its radius, and its roundedness (the
101    /// radius of the sphere used for dilating the cylinder).
102    #[cfg(feature = "dim3")]
103    pub fn round_cone(half_height: Real, radius: Real, border_radius: Real) -> Self {
104        SharedShape(Arc::new(RoundShape {
105            inner_shape: Cone::new(half_height, radius),
106            border_radius,
107        }))
108    }
109
110    /// Initialize a cone shape defined by its half-height
111    /// (along along the y axis) and its basis radius.
112    #[cfg(feature = "dim3")]
113    pub fn cone(half_height: Real, radius: Real) -> Self {
114        SharedShape(Arc::new(Cone::new(half_height, radius)))
115    }
116
117    /// Initialize a cuboid shape defined by its half-extents.
118    #[cfg(feature = "dim2")]
119    pub fn cuboid(hx: Real, hy: Real) -> Self {
120        SharedShape(Arc::new(Cuboid::new(Vector::new(hx, hy))))
121    }
122
123    /// Initialize a round cuboid shape defined by its half-extents and border radius.
124    #[cfg(feature = "dim2")]
125    pub fn round_cuboid(hx: Real, hy: Real, border_radius: Real) -> Self {
126        SharedShape(Arc::new(RoundShape {
127            inner_shape: Cuboid::new(Vector::new(hx, hy)),
128            border_radius,
129        }))
130    }
131
132    /// Initialize a cuboid shape defined by its half-extents.
133    #[cfg(feature = "dim3")]
134    pub fn cuboid(hx: Real, hy: Real, hz: Real) -> Self {
135        SharedShape(Arc::new(Cuboid::new(Vector::new(hx, hy, hz))))
136    }
137
138    /// Initialize a round cuboid shape defined by its half-extents and border radius.
139    #[cfg(feature = "dim3")]
140    pub fn round_cuboid(hx: Real, hy: Real, hz: Real, border_radius: Real) -> Self {
141        SharedShape(Arc::new(RoundShape {
142            inner_shape: Cuboid::new(Vector::new(hx, hy, hz)),
143            border_radius,
144        }))
145    }
146
147    /// Initialize a capsule shape from its endpoints and radius.
148    pub fn capsule(a: Point<Real>, b: Point<Real>, radius: Real) -> Self {
149        SharedShape(Arc::new(Capsule::new(a, b, radius)))
150    }
151
152    /// Initialize a capsule shape aligned with the `x` axis.
153    pub fn capsule_x(half_height: Real, radius: Real) -> Self {
154        let p = Point::from(Vector::x() * half_height);
155        Self::capsule(-p, p, radius)
156    }
157
158    /// Initialize a capsule shape aligned with the `y` axis.
159    pub fn capsule_y(half_height: Real, radius: Real) -> Self {
160        let p = Point::from(Vector::y() * half_height);
161        Self::capsule(-p, p, radius)
162    }
163
164    /// Initialize a capsule shape aligned with the `z` axis.
165    #[cfg(feature = "dim3")]
166    pub fn capsule_z(half_height: Real, radius: Real) -> Self {
167        let p = Point::from(Vector::z() * half_height);
168        Self::capsule(-p, p, radius)
169    }
170
171    /// Initialize a segment shape from its endpoints.
172    pub fn segment(a: Point<Real>, b: Point<Real>) -> Self {
173        SharedShape(Arc::new(Segment::new(a, b)))
174    }
175
176    /// Initializes a triangle shape.
177    pub fn triangle(a: Point<Real>, b: Point<Real>, c: Point<Real>) -> Self {
178        SharedShape(Arc::new(Triangle::new(a, b, c)))
179    }
180    /// Initializes a triangle shape with round corners.
181    pub fn round_triangle(
182        a: Point<Real>,
183        b: Point<Real>,
184        c: Point<Real>,
185        border_radius: Real,
186    ) -> Self {
187        SharedShape(Arc::new(RoundShape {
188            inner_shape: Triangle::new(a, b, c),
189            border_radius,
190        }))
191    }
192
193    /// Initializes a polyline shape defined by its vertex and index buffers.
194    ///
195    /// If no index buffer is provided, the polyline is assumed to describe a line strip.
196    pub fn polyline(vertices: Vec<Point<Real>>, indices: Option<Vec<[u32; 2]>>) -> Self {
197        SharedShape(Arc::new(Polyline::new(vertices, indices)))
198    }
199
200    /// Initializes a triangle mesh shape defined by its vertex and index buffers.
201    pub fn trimesh(
202        vertices: Vec<Point<Real>>,
203        indices: Vec<[u32; 3]>,
204    ) -> Result<Self, TriMeshBuilderError> {
205        Ok(SharedShape(Arc::new(TriMesh::new(vertices, indices)?)))
206    }
207
208    /// Initializes a triangle mesh shape defined by its vertex and index buffers and
209    /// pre-processing flags.
210    pub fn trimesh_with_flags(
211        vertices: Vec<Point<Real>>,
212        indices: Vec<[u32; 3]>,
213        flags: TriMeshFlags,
214    ) -> Result<Self, TriMeshBuilderError> {
215        Ok(SharedShape(Arc::new(TriMesh::with_flags(
216            vertices, indices, flags,
217        )?)))
218    }
219
220    /// Initializes a shape made of voxels.
221    ///
222    /// Each voxel has the size `voxel_size` and grid coordinate given by `grid_coords`.
223    /// The `primitive_geometry` controls the behavior of collision detection at voxels boundaries.
224    ///
225    /// For initializing a voxels shape from points in space, see [`Self::voxels_from_points`].
226    /// For initializing a voxels shape from a mesh to voxelize, see [`Self::voxelized_mesh`].
227    /// For initializing multiple voxels shape from the convex decomposition of a mesh, see
228    /// [`Self::voxelized_convex_decomposition`].
229    pub fn voxels(
230        primitive_geometry: VoxelPrimitiveGeometry,
231        voxel_size: Vector<Real>,
232        grid_coords: &[Point<i32>],
233    ) -> Self {
234        let shape = Voxels::new(primitive_geometry, voxel_size, grid_coords);
235        SharedShape::new(shape)
236    }
237
238    /// Initializes a shape made of voxels.
239    ///
240    /// Each voxel has the size `voxel_size` and contains at least one point from `centers`.
241    /// The `primitive_geometry` controls the behavior of collision detection at voxels boundaries.
242    pub fn voxels_from_points(
243        primitive_geometry: VoxelPrimitiveGeometry,
244        voxel_size: Vector<Real>,
245        points: &[Point<Real>],
246    ) -> Self {
247        let shape = Voxels::from_points(primitive_geometry, voxel_size, points);
248        SharedShape::new(shape)
249    }
250
251    /// Initializes a voxels shape obtained from the decomposition of the given trimesh (in 3D)
252    /// or polyline (in 2D) into voxelized convex parts.
253    pub fn voxelized_mesh(
254        primitive_geometry: VoxelPrimitiveGeometry,
255        vertices: &[Point<Real>],
256        indices: &[[u32; DIM]],
257        voxel_size: Real,
258        fill_mode: FillMode,
259    ) -> Self {
260        let mut voxels = VoxelSet::with_voxel_size(vertices, indices, voxel_size, fill_mode, true);
261        voxels.compute_bb();
262        Self::from_voxel_set(primitive_geometry, &voxels)
263    }
264
265    fn from_voxel_set(primitive_geometry: VoxelPrimitiveGeometry, vox_set: &VoxelSet) -> Self {
266        let centers: Vec<_> = vox_set
267            .voxels()
268            .iter()
269            .map(|v| vox_set.get_voxel_point(v))
270            .collect();
271        let shape =
272            Voxels::from_points(primitive_geometry, Vector::repeat(vox_set.scale), &centers);
273        SharedShape::new(shape)
274    }
275
276    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D)
277    /// or polyline (in 2D) into voxelized convex parts.
278    pub fn voxelized_convex_decomposition(
279        primitive_geometry: VoxelPrimitiveGeometry,
280        vertices: &[Point<Real>],
281        indices: &[[u32; DIM]],
282    ) -> Vec<Self> {
283        Self::voxelized_convex_decomposition_with_params(
284            primitive_geometry,
285            vertices,
286            indices,
287            &VHACDParameters::default(),
288        )
289    }
290
291    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D)
292    /// or polyline (in 2D) into voxelized convex parts.
293    pub fn voxelized_convex_decomposition_with_params(
294        primitive_geometry: VoxelPrimitiveGeometry,
295        vertices: &[Point<Real>],
296        indices: &[[u32; DIM]],
297        params: &VHACDParameters,
298    ) -> Vec<Self> {
299        let mut parts = vec![];
300        let decomp = VHACD::decompose(params, vertices, indices, true);
301
302        for vox_set in decomp.voxel_parts() {
303            parts.push(Self::from_voxel_set(primitive_geometry, vox_set));
304        }
305
306        parts
307    }
308
309    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or
310    /// polyline (in 2D) into convex parts.
311    pub fn convex_decomposition(vertices: &[Point<Real>], indices: &[[u32; DIM]]) -> Self {
312        Self::convex_decomposition_with_params(vertices, indices, &VHACDParameters::default())
313    }
314
315    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or
316    /// polyline (in 2D) into convex parts dilated with round corners.
317    pub fn round_convex_decomposition(
318        vertices: &[Point<Real>],
319        indices: &[[u32; DIM]],
320        border_radius: Real,
321    ) -> Self {
322        Self::round_convex_decomposition_with_params(
323            vertices,
324            indices,
325            &VHACDParameters::default(),
326            border_radius,
327        )
328    }
329
330    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or
331    /// polyline (in 2D) into convex parts.
332    pub fn convex_decomposition_with_params(
333        vertices: &[Point<Real>],
334        indices: &[[u32; DIM]],
335        params: &VHACDParameters,
336    ) -> Self {
337        let mut parts = vec![];
338        let decomp = VHACD::decompose(params, vertices, indices, true);
339
340        #[cfg(feature = "dim2")]
341        for vertices in decomp.compute_exact_convex_hulls(vertices, indices) {
342            if let Some(convex) = Self::convex_polyline(vertices) {
343                parts.push((Isometry::identity(), convex));
344            }
345        }
346
347        #[cfg(feature = "dim3")]
348        for (vertices, indices) in decomp.compute_exact_convex_hulls(vertices, indices) {
349            if let Some(convex) = Self::convex_mesh(vertices, &indices) {
350                parts.push((Isometry::identity(), convex));
351            }
352        }
353
354        Self::compound(parts)
355    }
356
357    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D) or
358    /// polyline (in 2D) into convex parts dilated with round corners.
359    pub fn round_convex_decomposition_with_params(
360        vertices: &[Point<Real>],
361        indices: &[[u32; DIM]],
362        params: &VHACDParameters,
363        border_radius: Real,
364    ) -> Self {
365        let mut parts = vec![];
366        let decomp = VHACD::decompose(params, vertices, indices, true);
367
368        #[cfg(feature = "dim2")]
369        for vertices in decomp.compute_exact_convex_hulls(vertices, indices) {
370            if let Some(convex) = Self::round_convex_polyline(vertices, border_radius) {
371                parts.push((Isometry::identity(), convex));
372            }
373        }
374
375        #[cfg(feature = "dim3")]
376        for (vertices, indices) in decomp.compute_exact_convex_hulls(vertices, indices) {
377            if let Some(convex) = Self::round_convex_mesh(vertices, &indices, border_radius) {
378                parts.push((Isometry::identity(), convex));
379            }
380        }
381
382        Self::compound(parts)
383    }
384
385    /// Creates a new shared shape that is the convex-hull of the given points.
386    pub fn convex_hull(points: &[Point<Real>]) -> Option<Self> {
387        #[cfg(feature = "dim2")]
388        return ConvexPolygon::from_convex_hull(points).map(|ch| SharedShape(Arc::new(ch)));
389        #[cfg(feature = "dim3")]
390        return ConvexPolyhedron::from_convex_hull(points).map(|ch| SharedShape(Arc::new(ch)));
391    }
392
393    /// Creates a new shared shape that is a 2D convex polygon from a set of points assumed to
394    /// describe a counter-clockwise convex polyline.
395    ///
396    /// This does **not** compute the convex-hull of the input points: convexity of the input is
397    /// assumed and not checked. For a version that calculates the convex hull of the input points,
398    /// use [`SharedShape::convex_hull`] instead.
399    ///
400    /// The generated [`ConvexPolygon`] will contain the given `points` with any point
401    /// collinear to the previous and next ones removed. For a version that leaves the input
402    /// `points` unmodified, use [`SharedShape::convex_polyline_unmodified`].
403    ///
404    /// Returns `None` if all points form an almost flat line.
405    #[cfg(feature = "dim2")]
406    pub fn convex_polyline(points: Vec<Point<Real>>) -> Option<Self> {
407        ConvexPolygon::from_convex_polyline(points).map(|ch| SharedShape(Arc::new(ch)))
408    }
409
410    /// Creates a new shared shape that is a 2D convex polygon from a set of points assumed to
411    /// describe a counter-clockwise convex polyline.
412    ///
413    /// This is the same as [`SharedShape::convex_polyline`] but without removing any point
414    /// from the input even if some are coplanar.
415    ///
416    /// Returns `None` if `points` doesn’t contain at least three points.
417    #[cfg(feature = "dim2")]
418    pub fn convex_polyline_unmodified(points: Vec<Point<Real>>) -> Option<Self> {
419        ConvexPolygon::from_convex_polyline_unmodified(points).map(|ch| SharedShape(Arc::new(ch)))
420    }
421
422    /// Creates a new shared shape that is a convex polyhedron formed by the
423    /// given set of points assumed to form a convex mesh (no convex-hull will be automatically
424    /// computed).
425    #[cfg(feature = "dim3")]
426    pub fn convex_mesh(points: Vec<Point<Real>>, indices: &[[u32; 3]]) -> Option<Self> {
427        ConvexPolyhedron::from_convex_mesh(points, indices).map(|ch| SharedShape(Arc::new(ch)))
428    }
429
430    /// Creates a new shared shape with rounded corners that is the
431    /// convex-hull of the given points, dilated by `border_radius`.
432    pub fn round_convex_hull(points: &[Point<Real>], border_radius: Real) -> Option<Self> {
433        #[cfg(feature = "dim2")]
434        return ConvexPolygon::from_convex_hull(points).map(|ch| {
435            SharedShape(Arc::new(RoundShape {
436                inner_shape: ch,
437                border_radius,
438            }))
439        });
440        #[cfg(feature = "dim3")]
441        return ConvexPolyhedron::from_convex_hull(points).map(|ch| {
442            SharedShape(Arc::new(RoundShape {
443                inner_shape: ch,
444                border_radius,
445            }))
446        });
447    }
448
449    /// Creates a new shared shape with round corners that is a convex polygon formed by the
450    /// given set of points assumed to form a convex polyline (no convex-hull will be automatically
451    /// computed).
452    #[cfg(feature = "dim2")]
453    pub fn round_convex_polyline(points: Vec<Point<Real>>, border_radius: Real) -> Option<Self> {
454        ConvexPolygon::from_convex_polyline(points).map(|ch| {
455            SharedShape(Arc::new(RoundShape {
456                inner_shape: ch,
457                border_radius,
458            }))
459        })
460    }
461
462    /// Creates a new shared shape with round corners that is a convex polyhedron formed by the
463    /// given set of points assumed to form a convex mesh (no convex-hull will be automatically
464    /// computed).
465    #[cfg(feature = "dim3")]
466    pub fn round_convex_mesh(
467        points: Vec<Point<Real>>,
468        indices: &[[u32; 3]],
469        border_radius: Real,
470    ) -> Option<Self> {
471        ConvexPolyhedron::from_convex_mesh(points, indices).map(|ch| {
472            SharedShape(Arc::new(RoundShape {
473                inner_shape: ch,
474                border_radius,
475            }))
476        })
477    }
478
479    /// Initializes a heightfield shape defined by its set of height and a scale
480    /// factor along each coordinate axis.
481    #[cfg(feature = "dim2")]
482    pub fn heightfield(heights: na::DVector<Real>, scale: Vector<Real>) -> Self {
483        SharedShape(Arc::new(HeightField::new(heights, scale)))
484    }
485
486    /// Initializes a heightfield shape on the x-z plane defined by its set of height and a scale
487    /// factor along each coordinate axis.
488    #[cfg(feature = "dim3")]
489    pub fn heightfield(heights: na::DMatrix<Real>, scale: Vector<Real>) -> Self {
490        SharedShape(Arc::new(HeightField::new(heights, scale)))
491    }
492
493    /// Initializes a heightfield shape on the x-z plane defined by its set of height, a scale
494    /// factor along each coordinate axis, and [`HeightFieldFlags`].
495    #[cfg(feature = "dim3")]
496    pub fn heightfield_with_flags(
497        heights: na::DMatrix<Real>,
498        scale: Vector<Real>,
499        flags: HeightFieldFlags,
500    ) -> Self {
501        SharedShape(Arc::new(HeightField::with_flags(heights, scale, flags)))
502    }
503}
504
505#[cfg(feature = "serde-serialize")]
506impl serde::Serialize for SharedShape {
507    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
508    where
509        S: serde::Serializer,
510    {
511        self.0.as_typed_shape().serialize(serializer)
512    }
513}
514
515#[cfg(feature = "serde-serialize")]
516impl<'de> serde::Deserialize<'de> for SharedShape {
517    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
518    where
519        D: serde::Deserializer<'de>,
520    {
521        use crate::serde::de::Error;
522        DeserializableTypedShape::deserialize(deserializer)?
523            .into_shared_shape()
524            .ok_or(D::Error::custom("Cannot deserialize custom shape."))
525    }
526}