parry3d/shape/
shared_shape.rs

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