Skip to main content

avian3d/collision/collider/parry/
mod.rs

1#![allow(clippy::unnecessary_cast)]
2
3pub mod contact_query;
4
5#[cfg(feature = "2d")]
6mod primitives2d;
7#[cfg(feature = "3d")]
8mod primitives3d;
9
10#[cfg(feature = "2d")]
11pub use primitives2d::{EllipseColliderShape, RegularPolygonColliderShape};
12
13use super::EnlargedAabb;
14use crate::{make_pose, math::Real, prelude::*};
15#[cfg(feature = "collider-from-mesh")]
16use bevy::mesh::{Indices, VertexAttributeValues};
17use bevy::{log, prelude::*};
18use contact_query::UnsupportedShape;
19use itertools::Either;
20use parry::{
21    bounding_volume::BoundingVolume,
22    shape::{RoundShape, SharedShape, TypedShape, Voxels},
23};
24
25impl<T: IntoCollider<Collider>> From<T> for Collider {
26    fn from(value: T) -> Self {
27        value.collider()
28    }
29}
30
31/// Returns `true` if the given shape supports CCD sweeps.
32pub(crate) fn shape_supports_sweeps(shape: &dyn parry::shape::Shape) -> bool {
33    !matches!(
34        shape.as_typed_shape(),
35        TypedShape::TriMesh(_)
36            | TypedShape::Polyline(_)
37            | TypedShape::HeightField(_)
38            | TypedShape::Voxels(_)
39            | TypedShape::Segment(_)
40            | TypedShape::Triangle(_)
41    )
42}
43
44/// Parameters controlling the VHACD convex decomposition.
45///
46/// See <https://github.com/Unity-Technologies/VHACD#parameters> for details.
47#[derive(Clone, PartialEq, Debug, Reflect)]
48#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
49#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
50#[reflect(PartialEq, Debug)]
51pub struct VhacdParameters {
52    /// Maximum concavity.
53    ///
54    /// Default: 0.1 (in 2D), 0.01 (in 3D).
55    /// Valid range `[0.0, 1.0]`.
56    pub concavity: Real,
57    /// Controls the bias toward clipping along symmetry planes.
58    ///
59    /// Default: 0.05.
60    /// Valid Range: `[0.0, 1.0]`.
61    pub alpha: Real,
62    /// Controls the bias toward clipping along revolution planes.
63    ///
64    /// Default: 0.05.
65    /// Valid Range: `[0.0, 1.0]`.
66    pub beta: Real,
67    /// Resolution used during the voxelization stage.
68    ///
69    /// Default: 256 (in 2D), 64 (in 3D).
70    pub resolution: u32,
71    /// Controls the granularity of the search for the best
72    /// clipping plane during the decomposition.
73    ///
74    /// Default: 4
75    pub plane_downsampling: u32,
76    /// Controls the precision of the convex-hull generation
77    /// process during the clipping plane selection stage.
78    ///
79    /// Default: 4
80    pub convex_hull_downsampling: u32,
81    /// Controls the way the input mesh or polyline is being
82    /// voxelized.
83    ///
84    /// Default: `FillMode::FloodFill { detect_cavities: false, detect_self_intersections: false }`
85    pub fill_mode: FillMode,
86    /// Controls whether the convex-hull should be approximated during the decomposition stage.
87    /// Setting this to `true` increases performances with a slight degradation of the decomposition
88    /// quality.
89    ///
90    /// Default: true
91    pub convex_hull_approximation: bool,
92    /// Controls the max number of convex-hull generated by the convex decomposition.
93    ///
94    /// Default: 1024
95    pub max_convex_hulls: u32,
96}
97
98impl Default for VhacdParameters {
99    fn default() -> Self {
100        Self {
101            #[cfg(feature = "3d")]
102            resolution: 64,
103            #[cfg(feature = "3d")]
104            concavity: 0.01,
105            #[cfg(feature = "2d")]
106            resolution: 256,
107            #[cfg(feature = "2d")]
108            concavity: 0.1,
109            plane_downsampling: 4,
110            convex_hull_downsampling: 4,
111            alpha: 0.05,
112            beta: 0.05,
113            convex_hull_approximation: true,
114            max_convex_hulls: 1024,
115            fill_mode: FillMode::FloodFill {
116                detect_cavities: false,
117                #[cfg(feature = "2d")]
118                detect_self_intersections: false,
119            },
120        }
121    }
122}
123
124impl From<VhacdParameters> for parry::transformation::vhacd::VHACDParameters {
125    fn from(value: VhacdParameters) -> Self {
126        Self {
127            concavity: value.concavity,
128            alpha: value.alpha,
129            beta: value.beta,
130            resolution: value.resolution,
131            plane_downsampling: value.plane_downsampling,
132            convex_hull_downsampling: value.convex_hull_downsampling,
133            fill_mode: value.fill_mode.into(),
134            convex_hull_approximation: value.convex_hull_approximation,
135            max_convex_hulls: value.max_convex_hulls,
136        }
137    }
138}
139
140/// Controls how the voxelization determines which voxel needs
141/// to be considered empty, and which ones will be considered full.
142#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
143#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
144#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
145#[reflect(Hash, PartialEq, Debug)]
146pub enum FillMode {
147    /// Only consider full the voxels intersecting the surface of the
148    /// shape being voxelized.
149    SurfaceOnly,
150    /// Use a flood-fill technique to consider fill the voxels intersecting
151    /// the surface of the shape being voxelized, as well as all the voxels
152    /// bounded of them.
153    FloodFill {
154        /// Detects holes inside of a solid contour.
155        detect_cavities: bool,
156        /// Attempts to properly handle self-intersections.
157        #[cfg(feature = "2d")]
158        detect_self_intersections: bool,
159    },
160}
161
162impl From<FillMode> for parry::transformation::voxelization::FillMode {
163    fn from(value: FillMode) -> Self {
164        match value {
165            FillMode::SurfaceOnly => Self::SurfaceOnly,
166            FillMode::FloodFill {
167                detect_cavities,
168                #[cfg(feature = "2d")]
169                detect_self_intersections,
170            } => Self::FloodFill {
171                detect_cavities,
172                #[cfg(feature = "2d")]
173                detect_self_intersections,
174            },
175        }
176    }
177}
178
179/// Flags used for the preprocessing of a triangle mesh collider.
180#[repr(transparent)]
181#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
182#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
183#[reflect(opaque, Hash, PartialEq, Debug)]
184pub struct TrimeshFlags(u8);
185
186bitflags::bitflags! {
187    impl TrimeshFlags: u8 {
188        /// If set, the half-edge topology of the trimesh will be computed if possible.
189        const HALF_EDGE_TOPOLOGY = 0b0000_0001;
190        /// If set, the half-edge topology and connected components of the trimesh will be computed if possible.
191        ///
192        /// Because of the way it is currently implemented, connected components can only be computed on
193        /// a mesh where the half-edge topology computation succeeds. It will no longer be the case in the
194        /// future once we decouple the computations.
195        const CONNECTED_COMPONENTS = 0b0000_0010;
196        /// If set, any triangle that results in a failing half-hedge topology computation will be deleted.
197        const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
198        /// If set, the trimesh will be assumed to be oriented (with outward normals).
199        ///
200        /// The pseudo-normals of its vertices and edges will be computed.
201        const ORIENTED = 0b0000_1000;
202        /// If set, the duplicate vertices of the trimesh will be merged.
203        ///
204        /// Two vertices with the exact same coordinates will share the same entry on the
205        /// vertex buffer and the index buffer is adjusted accordingly.
206        const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
207        /// If set, the triangles sharing two vertices with identical index values will be removed.
208        ///
209        /// Because of the way it is currently implemented, this methods implies that duplicate
210        /// vertices will be merged. It will no longer be the case in the future once we decouple
211        /// the computations.
212        const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
213        /// If set, two triangles sharing three vertices with identical index values (in any order) will be removed.
214        ///
215        /// Because of the way it is currently implemented, this methods implies that duplicate
216        /// vertices will be merged. It will no longer be the case in the future once we decouple
217        /// the computations.
218        const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
219        /// If set, a special treatment will be applied to contact manifold calculation to eliminate
220        /// or fix contacts normals that could lead to incorrect bumps in physics simulation
221        /// (especially on flat surfaces).
222        ///
223        /// This is achieved by taking into account adjacent triangle normals when computing contact
224        /// points for a given triangle.
225        const FIX_INTERNAL_EDGES = 0b1000_0000 | Self::ORIENTED.bits() | Self::MERGE_DUPLICATE_VERTICES.bits();
226    }
227}
228
229impl From<TrimeshFlags> for parry::shape::TriMeshFlags {
230    fn from(value: TrimeshFlags) -> Self {
231        Self::from_bits(value.bits().into()).unwrap()
232    }
233}
234
235/// An error indicating an inconsistency when building a triangle mesh collider.
236pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError;
237
238/// A collider used for detecting collisions and generating contacts.
239///
240/// # Creation
241///
242/// `Collider` has tons of methods for creating colliders of various shapes:
243///
244/// ```
245#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
246#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
247/// # use bevy::prelude::*;
248/// #
249/// # fn setup(mut commands: Commands) {
250/// // Create a ball collider with a given radius
251#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
252#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
253/// // Create a capsule collider with a given radius and height
254/// commands.spawn(Collider::capsule(0.5, 2.0));
255/// # }
256/// ```
257///
258/// Colliders on their own only detect contacts and generate
259/// [collision events](crate::collision#collision-events).
260/// To make colliders apply contact forces, they have to be attached
261/// to [rigid bodies](RigidBody):
262///
263/// ```
264#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
265#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
266/// use bevy::prelude::*;
267///
268/// // Spawn a dynamic body that falls onto a static platform
269/// fn setup(mut commands: Commands) {
270///     commands.spawn((
271///         RigidBody::Dynamic,
272#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.5),")]
273#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.5),")]
274///         Transform::from_xyz(0.0, 2.0, 0.0),
275///     ));
276#[cfg_attr(
277    feature = "2d",
278    doc = "    commands.spawn((RigidBody::Static, Collider::rectangle(5.0, 0.5)));"
279)]
280#[cfg_attr(
281    feature = "3d",
282    doc = "    commands.spawn((RigidBody::Static, Collider::cuboid(5.0, 0.5, 5.0)));"
283)]
284/// }
285/// ```
286///
287/// Colliders can be further configured using various components like [`Friction`], [`Restitution`],
288/// [`Sensor`], [`CollisionLayers`], [`CollisionMargin`], and [`ColliderDensity`].
289///
290/// If you need to specify the shape of the collider statically, use [`ColliderConstructor`] and build your collider
291/// with the [`Collider::try_from_constructor`] method.
292/// This can also be done automatically by simply placing the [`ColliderConstructor`] on an entity.
293///
294#[cfg_attr(
295    feature = "3d",
296    doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
297)]
298///
299/// ## Multiple Colliders
300///
301/// It can often be useful to attach multiple colliders to the same rigid body.
302///
303/// This can be done in two ways. Either use [`Collider::compound`] to have one collider that consists of many
304/// shapes, or for more control, spawn several collider entities as the children of a rigid body:
305///
306/// ```
307#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
308#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
309/// use bevy::prelude::*;
310///
311/// fn setup(mut commands: Commands) {
312///     // Spawn a rigid body with one collider on the same entity and two as children
313///     commands
314#[cfg_attr(
315    feature = "2d",
316    doc = "        .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
317)]
318#[cfg_attr(
319    feature = "3d",
320    doc = "        .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
321)]
322///         .with_children(|children| {
323///             // Spawn the child colliders positioned relative to the rigid body
324#[cfg_attr(
325    feature = "2d",
326    doc = "            children.spawn((Collider::circle(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
327            children.spawn((Collider::circle(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
328)]
329#[cfg_attr(
330    feature = "3d",
331    doc = "            children.spawn((Collider::sphere(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
332            children.spawn((Collider::sphere(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
333)]
334///         });
335/// }
336/// ```
337///
338/// Colliders can be arbitrarily nested and transformed relative to the parent.
339/// The rigid body that a collider is attached to can be accessed using the [`ColliderOf`] component.
340///
341/// The benefit of using separate entities for the colliders is that each collider can have its own
342/// [friction](Friction), [restitution](Restitution), [collision layers](CollisionLayers),
343/// and other configuration options, and they send separate [collision events](crate::collision#collision-events).
344///
345/// # See More
346///
347/// - [Rigid bodies](RigidBody)
348/// - [Density](ColliderDensity)
349/// - [Friction] and [restitution](Restitution) (bounciness)
350/// - [Collision layers](CollisionLayers)
351/// - [Sensors](Sensor)
352/// - [Collision margins for adding extra thickness to colliders](CollisionMargin)
353#[cfg_attr(
354    feature = "3d",
355    doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
356)]
357/// - [Get colliding entities](CollidingEntities)
358/// - [Collision events](crate::collision#collision-events)
359/// - [Accessing collision data](Collisions)
360/// - [Filtering and modifying contacts with hooks](CollisionHooks)
361/// - [Manual contact queries](contact_query)
362///
363/// # Advanced Usage
364///
365/// Internally, `Collider` uses the shapes provided by `parry`. If you want to create a collider
366/// using these shapes, you can simply use `Collider::from(SharedShape::some_method())`.
367///
368/// To get a reference to the internal [`SharedShape`], you can use the [`Collider::shape()`]
369/// or [`Collider::shape_scaled()`] methods.
370///
371/// `Collider` is currently not `Reflect`. If you need to reflect it, you can use [`ColliderConstructor`] as a workaround.
372#[derive(Clone, Component, Debug)]
373#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
374#[require(
375    ColliderMarker,
376    ColliderAabb,
377    CollisionLayers,
378    EnlargedAabb,
379    ColliderDensity,
380    ColliderMassProperties
381)]
382pub struct Collider {
383    /// The raw unscaled collider shape.
384    shape: SharedShape,
385    /// The scaled version of the collider shape.
386    ///
387    /// If the scale is one, this will be `None` and `unscaled_shape`
388    /// will be used instead.
389    scaled_shape: SharedShape,
390    /// The global scale used for the collider shape.
391    scale: Vector,
392}
393
394impl From<SharedShape> for Collider {
395    fn from(value: SharedShape) -> Self {
396        Self {
397            shape: value.clone(),
398            scaled_shape: value,
399            scale: Vector::ONE,
400        }
401    }
402}
403
404impl Default for Collider {
405    fn default() -> Self {
406        #[cfg(feature = "2d")]
407        {
408            Self::rectangle(0.5, 0.5)
409        }
410        #[cfg(feature = "3d")]
411        {
412            Self::cuboid(0.5, 0.5, 0.5)
413        }
414    }
415}
416
417impl AnyCollider for Collider {
418    type Context = ();
419
420    fn aabb_with_context(
421        &self,
422        position: RVector,
423        rotation: impl Into<Rot>,
424        margin: f32,
425        _: ColliderContext<Self::Context>,
426    ) -> ColliderAabb {
427        let aabb = self
428            .shape_scaled()
429            .compute_aabb(&make_pose(position, rotation))
430            .loosened(margin.real());
431        ColliderAabb::from_min_max(aabb.mins, aabb.maxs)
432    }
433
434    fn contact_manifolds_with_context(
435        &self,
436        other: &Self,
437        position1: RVector,
438        rotation1: impl Into<Rot>,
439        position2: RVector,
440        rotation2: impl Into<Rot>,
441        prediction_distance: f32,
442        manifolds: &mut Vec<ContactManifold>,
443        _: ColliderPairContext<Self::Context>,
444    ) {
445        contact_query::contact_manifolds(
446            self,
447            position1,
448            rotation1,
449            other,
450            position2,
451            rotation2,
452            prediction_distance,
453            manifolds,
454        )
455    }
456
457    fn ccd_thickness_with_context(&self, _context: ColliderContext<Self::Context>) -> f32 {
458        if !shape_supports_sweeps(self.shape_scaled().as_ref()) {
459            return f32::INFINITY;
460        }
461
462        self.shape_scaled().ccd_thickness().f32()
463    }
464
465    fn max_distance_to_point_with_context(
466        &self,
467        point: RVector,
468        _context: ColliderContext<Self::Context>,
469    ) -> f32 {
470        let bounding_sphere = self.shape_scaled().compute_local_bounding_sphere();
471        point.distance(bounding_sphere.center).f32() + bounding_sphere.radius.f32()
472    }
473
474    fn bounding_radius_with_context(&self, _context: ColliderContext<Self::Context>) -> f32 {
475        let center_of_mass = self.center_of_mass();
476        self.max_distance_to_point(center_of_mass.real())
477    }
478}
479
480// TODO: `bevy_heavy` supports computing the individual mass properties efficiently for Bevy's primitive shapes,
481//       but Parry doesn't support it for its own shapes, so we have to compute all mass properties in each method :(
482#[cfg(feature = "2d")]
483impl ComputeMassProperties for Collider {
484    fn mass(&self, density: f32) -> f32 {
485        let props = self.shape_scaled().mass_properties(density.real());
486        props.mass().f32()
487    }
488
489    fn unit_angular_inertia(&self) -> f32 {
490        self.angular_inertia(1.0)
491    }
492
493    fn angular_inertia(&self, mass: f32) -> f32 {
494        let props = self.shape_scaled().mass_properties(mass.real());
495        props.principal_inertia().f32()
496    }
497
498    fn center_of_mass(&self) -> Vec2 {
499        let props = self.shape_scaled().mass_properties(1.0);
500        props.local_com.f32()
501    }
502
503    fn mass_properties(&self, density: f32) -> MassProperties {
504        let props = self.shape_scaled().mass_properties(density.real());
505
506        MassProperties {
507            mass: props.mass().f32(),
508            #[cfg(feature = "2d")]
509            angular_inertia: props.principal_inertia().f32(),
510            #[cfg(feature = "3d")]
511            principal_angular_inertia: props.principal_inertia().f32(),
512            #[cfg(feature = "3d")]
513            local_inertial_frame: props.principal_inertia_local_frame.f32(),
514            center_of_mass: props.local_com.f32(),
515        }
516    }
517}
518
519#[cfg(feature = "3d")]
520impl ComputeMassProperties for Collider {
521    fn mass(&self, density: f32) -> f32 {
522        let props = self.shape_scaled().mass_properties(density.real());
523        props.mass().f32()
524    }
525
526    fn unit_principal_angular_inertia(&self) -> Vec3 {
527        self.principal_angular_inertia(1.0)
528    }
529
530    fn principal_angular_inertia(&self, mass: f32) -> Vec3 {
531        let props = self.shape_scaled().mass_properties(mass.real());
532        props.principal_inertia().f32()
533    }
534
535    fn local_inertial_frame(&self) -> Quat {
536        let props = self.shape_scaled().mass_properties(1.0);
537        props.principal_inertia_local_frame.f32()
538    }
539
540    fn center_of_mass(&self) -> Vec3 {
541        let props = self.shape_scaled().mass_properties(1.0);
542        props.local_com.f32()
543    }
544
545    fn mass_properties(&self, density: f32) -> MassProperties {
546        let props = self.shape_scaled().mass_properties(density.real());
547
548        MassProperties {
549            mass: props.mass().f32(),
550            #[cfg(feature = "2d")]
551            angular_inertia: props.principal_inertia().f32(),
552            #[cfg(feature = "3d")]
553            principal_angular_inertia: props.principal_inertia().f32(),
554            #[cfg(feature = "3d")]
555            local_inertial_frame: props.principal_inertia_local_frame.f32(),
556            center_of_mass: props.local_com.f32(),
557        }
558    }
559}
560
561impl ScalableCollider for Collider {
562    fn scale(&self) -> Vector {
563        self.scale()
564    }
565
566    fn set_scale(&mut self, scale: Vector, detail: u32) {
567        self.set_scale(scale, detail)
568    }
569}
570
571impl Collider {
572    /// Returns the raw unscaled shape of the collider.
573    pub fn shape(&self) -> &SharedShape {
574        &self.shape
575    }
576
577    /// Returns a mutable reference to the raw unscaled shape of the collider.
578    pub fn shape_mut(&mut self) -> &mut SharedShape {
579        &mut self.shape
580    }
581
582    /// Returns the shape of the collider with the scale from its `GlobalTransform` applied.
583    pub fn shape_scaled(&self) -> &SharedShape {
584        &self.scaled_shape
585    }
586
587    /// Sets the unscaled shape of the collider. The collider's scale will be applied to this shape.
588    pub fn set_shape(&mut self, shape: SharedShape) {
589        self.shape = shape;
590
591        // TODO: The number of subdivisions probably shouldn't be hard-coded
592        if let Ok(scaled) = scale_shape(&self.shape, self.scale, 10) {
593            self.scaled_shape = scaled;
594        } else {
595            log::error!("Failed to create convex hull for scaled collider.");
596        }
597    }
598
599    /// Returns the global scale of the collider.
600    pub fn scale(&self) -> Vector {
601        self.scale
602    }
603
604    /// Set the global scaling factor of this shape.
605    ///
606    /// If the scaling factor is not uniform, and the scaled shape can’t be
607    /// represented as a supported shape, the shape is approximated as
608    /// a convex polygon or polyhedron using `num_subdivisions`.
609    ///
610    /// For example, if a ball was scaled to an ellipse, the new shape would be approximated.
611    pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
612        if scale == self.scale {
613            return;
614        }
615
616        if scale == Vector::ONE {
617            // Trivial case.
618            self.scaled_shape = self.shape.clone();
619            self.scale = Vector::ONE;
620            return;
621        }
622
623        if let Ok(scaled) = scale_shape(&self.shape, scale, num_subdivisions) {
624            self.scaled_shape = scaled;
625            self.scale = scale;
626        } else {
627            log::error!("Failed to create convex hull for scaled collider.");
628        }
629    }
630
631    /// Projects the given `point` onto `self` transformed by `translation` and `rotation`.
632    /// The returned tuple contains the projected point and whether it is inside the collider.
633    ///
634    /// If `solid` is true and the given `point` is inside of the collider, the projection will be at the point.
635    /// Otherwise, the collider will be treated as hollow, and the projection will be at the collider's boundary.
636    pub fn project_point(
637        &self,
638        translation: RVector,
639        rotation: impl Into<Rot>,
640        point: RVector,
641        solid: bool,
642    ) -> (RVector, bool) {
643        let projection =
644            self.shape_scaled()
645                .project_point(&make_pose(translation, rotation), point, solid);
646        (projection.point, projection.is_inside)
647    }
648
649    /// Computes the minimum distance between the given `point` and `self` transformed by `translation` and `rotation`.
650    ///
651    /// If `solid` is true and the given `point` is inside of the collider, the returned distance will be `0.0`.
652    /// Otherwise, the collider will be treated as hollow, and the distance will be the distance
653    /// to the collider's boundary.
654    pub fn distance_to_point(
655        &self,
656        translation: RVector,
657        rotation: impl Into<Rot>,
658        point: RVector,
659        solid: bool,
660    ) -> f32 {
661        self.shape_scaled()
662            .distance_to_point(&make_pose(translation, rotation), point, solid)
663            .f32()
664    }
665
666    /// Tests whether the given `point` is inside of `self` transformed by `translation` and `rotation`.
667    pub fn contains_point(
668        &self,
669        translation: RVector,
670        rotation: impl Into<Rot>,
671        point: RVector,
672    ) -> bool {
673        self.shape_scaled()
674            .contains_point(&make_pose(translation, rotation), point)
675    }
676
677    /// Computes the distance and normal between the given ray and `self`
678    /// transformed by `translation` and `rotation`.
679    ///
680    /// The returned tuple is in the format `(distance, normal)`.
681    ///
682    /// # Arguments
683    ///
684    /// - `ray_origin`: Where the ray is cast from.
685    /// - `ray_direction`: What direction the ray is cast in.
686    /// - `max_distance`: The maximum distance the ray can travel.
687    /// - `solid`: If true and the ray origin is inside of a collider, the hit point will be the ray origin itself.
688    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at the collider's boundary.
689    pub fn cast_ray(
690        &self,
691        translation: RVector,
692        rotation: impl Into<Rot>,
693        ray_origin: RVector,
694        ray_direction: Vector,
695        max_distance: f32,
696        solid: bool,
697    ) -> Option<(f32, Vector)> {
698        let hit = self.shape_scaled().cast_ray_and_get_normal(
699            &make_pose(translation, rotation),
700            &parry::query::Ray::new(ray_origin, ray_direction.real()),
701            max_distance.real(),
702            solid,
703        );
704        hit.map(|hit| (hit.time_of_impact.f32(), hit.normal.f32()))
705    }
706
707    /// Tests whether the given ray intersects `self` transformed by `translation` and `rotation`.
708    ///
709    /// # Arguments
710    ///
711    /// - `ray_origin`: Where the ray is cast from.
712    /// - `ray_direction`: What direction the ray is cast in.
713    /// - `max_distance`: The maximum distance the ray can travel.
714    pub fn intersects_ray(
715        &self,
716        translation: RVector,
717        rotation: impl Into<Rot>,
718        ray_origin: RVector,
719        ray_direction: Vector,
720        max_distance: f32,
721    ) -> bool {
722        self.shape_scaled().intersects_ray(
723            &make_pose(translation, rotation),
724            &parry::query::Ray::new(ray_origin, ray_direction.real()),
725            max_distance.real(),
726        )
727    }
728
729    /// Creates a collider with a compound shape defined by a given vector of colliders with a position and a rotation.
730    ///
731    /// Especially for dynamic rigid bodies, compound shape colliders should be preferred over triangle meshes and polylines,
732    /// because convex shapes typically provide more reliable results.
733    ///
734    /// If you want to create a compound shape from a 3D triangle mesh or 2D polyline, consider using the
735    /// [`Collider::convex_decomposition`] method.
736    pub fn compound(
737        shapes: Vec<(
738            impl Into<Position>,
739            impl Into<Rotation>,
740            impl Into<Collider>,
741        )>,
742    ) -> Self {
743        let shapes = shapes
744            .into_iter()
745            .map(|(p, r, c)| {
746                (
747                    make_pose(p.into(), r.into()),
748                    c.into().shape_scaled().clone(),
749                )
750            })
751            .collect::<Vec<_>>();
752        SharedShape::compound(shapes).into()
753    }
754
755    /// Creates a collider with a circle shape defined by its radius.
756    #[cfg(feature = "2d")]
757    pub fn circle(radius: f32) -> Self {
758        SharedShape::ball(radius.real()).into()
759    }
760
761    /// Creates a collider with a sphere shape defined by its radius.
762    #[cfg(feature = "3d")]
763    pub fn sphere(radius: f32) -> Self {
764        SharedShape::ball(radius.real()).into()
765    }
766
767    /// Creates a collider with an ellipse shape defined by a half-width and half-height.
768    #[cfg(feature = "2d")]
769    pub fn ellipse(half_width: f32, half_height: f32) -> Self {
770        SharedShape::new(EllipseColliderShape(Ellipse::new(
771            half_width as f32,
772            half_height as f32,
773        )))
774        .into()
775    }
776
777    /// Creates a collider with a rectangle shape defined by its extents.
778    #[cfg(feature = "2d")]
779    pub fn rectangle(x_length: f32, y_length: f32) -> Self {
780        SharedShape::cuboid(x_length.real() * 0.5, y_length.real() * 0.5).into()
781    }
782
783    /// Creates a collider with a cuboid shape defined by its extents.
784    #[cfg(feature = "3d")]
785    pub fn cuboid(x_length: f32, y_length: f32, z_length: f32) -> Self {
786        SharedShape::cuboid(
787            x_length.real() * 0.5,
788            y_length.real() * 0.5,
789            z_length.real() * 0.5,
790        )
791        .into()
792    }
793
794    /// Creates a collider with a rectangle shape defined by its extents and rounded corners.
795    #[cfg(feature = "2d")]
796    pub fn round_rectangle(x_length: f32, y_length: f32, border_radius: f32) -> Self {
797        SharedShape::round_cuboid(
798            x_length.real() * 0.5,
799            y_length.real() * 0.5,
800            border_radius.real(),
801        )
802        .into()
803    }
804
805    /// Creates a collider with a cuboid shape defined by its extents and rounded corners.
806    #[cfg(feature = "3d")]
807    pub fn round_cuboid(x_length: f32, y_length: f32, z_length: f32, border_radius: f32) -> Self {
808        SharedShape::round_cuboid(
809            x_length.real() * 0.5,
810            y_length.real() * 0.5,
811            z_length.real() * 0.5,
812            border_radius.real(),
813        )
814        .into()
815    }
816
817    /// Creates a collider with a cylinder shape defined by its radius
818    /// on the `XZ` plane and its height along the `Y` axis.
819    #[cfg(feature = "3d")]
820    pub fn cylinder(radius: f32, height: f32) -> Self {
821        SharedShape::cylinder(height.real() * 0.5, radius.real()).into()
822    }
823
824    /// Creates a collider with a cone shape defined by the radius of its base
825    /// on the `XZ` plane and its height along the `Y` axis.
826    #[cfg(feature = "3d")]
827    pub fn cone(radius: f32, height: f32) -> Self {
828        SharedShape::cone(height.real() * 0.5, radius.real()).into()
829    }
830
831    /// Creates a collider with a capsule shape defined by its radius
832    /// and its height along the `Y` axis, excluding the hemispheres.
833    pub fn capsule(radius: f32, length: f32) -> Self {
834        SharedShape::capsule(
835            RVector::Y * length.real() * 0.5,
836            RVector::NEG_Y * length.real() * 0.5,
837            radius.real(),
838        )
839        .into()
840    }
841
842    /// Creates a collider with a capsule shape defined by its radius and endpoints `a` and `b`.
843    pub fn capsule_endpoints(radius: f32, a: Vector, b: Vector) -> Self {
844        SharedShape::capsule(a.real(), b.real(), radius.real()).into()
845    }
846
847    /// Creates a collider with a [half-space](https://en.wikipedia.org/wiki/Half-space_(geometry)) shape
848    /// defined by the outward normal of its planar boundary.
849    pub fn half_space(outward_normal: Vector) -> Self {
850        SharedShape::halfspace(outward_normal.normalize_or_zero().real()).into()
851    }
852
853    /// Creates a collider with a segment shape defined by its endpoints `a` and `b`.
854    pub fn segment(a: Vector, b: Vector) -> Self {
855        SharedShape::segment(a.real(), b.real()).into()
856    }
857
858    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
859    ///
860    /// If the triangle is oriented clockwise, it will be reversed to be counterclockwise
861    /// by swapping `b` and `c`. This is needed for collision detection.
862    ///
863    /// If you know that the given points produce a counterclockwise triangle,
864    /// consider using [`Collider::triangle_unchecked`] instead.
865    #[cfg(feature = "2d")]
866    pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
867        let mut triangle = parry::shape::Triangle::new(a.real(), b.real(), c.real());
868
869        // Make sure the triangle is counterclockwise. This is needed for collision detection.
870        if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
871            triangle.reverse();
872        }
873
874        SharedShape::new(triangle).into()
875    }
876
877    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
878    ///
879    /// The orientation of the triangle is assumed to be counterclockwise.
880    /// This is needed for collision detection.
881    ///
882    /// If you are unsure about the orientation of the triangle, consider using [`Collider::triangle`] instead.
883    #[cfg(feature = "2d")]
884    pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
885        SharedShape::triangle(a.real(), b.real(), c.real()).into()
886    }
887
888    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
889    #[cfg(feature = "3d")]
890    pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
891        SharedShape::triangle(a.real(), b.real(), c.real()).into()
892    }
893
894    /// Creates a collider with a regular polygon shape defined by the circumradius and the number of sides.
895    #[cfg(feature = "2d")]
896    pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
897        RegularPolygon::new(circumradius, sides).collider()
898    }
899
900    /// Creates a collider with a polyline shape defined by its vertices and optionally an index buffer.
901    pub fn polyline(vertices: Vec<RVector>, indices: Option<Vec<[u32; 2]>>) -> Self {
902        SharedShape::polyline(vertices, indices).into()
903    }
904
905    /// Creates a collider with a triangle mesh shape defined by its vertex and index buffers.
906    ///
907    /// Note that the resulting collider will be hollow and have no interior.
908    /// This makes it more prone to tunneling and other collision issues.
909    ///
910    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
911    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
912    ///
913    /// # Panics
914    ///
915    /// Panics if the given vertex and index buffers do not contain any triangles,
916    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
917    pub fn trimesh(vertices: Vec<RVector>, indices: Vec<[u32; 3]>) -> Self {
918        Self::try_trimesh(vertices, indices)
919            .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
920    }
921
922    /// Tries to create a collider with a triangle mesh shape defined by its vertex and index buffers.
923    ///
924    /// Note that the resulting collider will be hollow and have no interior.
925    /// This makes it more prone to tunneling and other collision issues.
926    ///
927    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
928    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
929    ///
930    /// # Errors
931    ///
932    /// Returns a [`TrimeshBuilderError`] if the given vertex and index buffers do not contain any triangles,
933    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
934    pub fn try_trimesh(
935        vertices: Vec<RVector>,
936        indices: Vec<[u32; 3]>,
937    ) -> Result<Self, TrimeshBuilderError> {
938        SharedShape::trimesh(vertices, indices).map(|trimesh| trimesh.into())
939    }
940
941    /// Creates a collider with a triangle mesh shape defined by its vertex and index buffers
942    /// and flags controlling the preprocessing.
943    ///
944    /// Note that the resulting collider will be hollow and have no interior.
945    /// This makes it more prone to tunneling and other collision issues.
946    ///
947    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
948    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
949    ///
950    /// # Panics
951    ///
952    /// Panics if after preprocessing the given vertex and index buffers do not contain any triangles,
953    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
954    pub fn trimesh_with_config(
955        vertices: Vec<RVector>,
956        indices: Vec<[u32; 3]>,
957        flags: TrimeshFlags,
958    ) -> Self {
959        Self::try_trimesh_with_config(vertices, indices, flags)
960            .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
961    }
962
963    /// Tries to create a collider with a triangle mesh shape defined by its vertex and index buffers
964    /// and flags controlling the preprocessing.
965    ///
966    /// Note that the resulting collider will be hollow and have no interior.
967    /// This makes it more prone to tunneling and other collision issues.
968    ///
969    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
970    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
971    ///
972    /// # Errors
973    ///
974    /// Returns a [`TrimeshBuilderError`] if after preprocessing the given vertex and index buffers do not contain any triangles,
975    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
976    pub fn try_trimesh_with_config(
977        vertices: Vec<RVector>,
978        indices: Vec<[u32; 3]>,
979        flags: TrimeshFlags,
980    ) -> Result<Self, TrimeshBuilderError> {
981        SharedShape::trimesh_with_flags(vertices, indices, flags.into())
982            .map(|trimesh| trimesh.into())
983    }
984
985    /// Creates a collider shape with a compound shape obtained from the decomposition of a given polyline
986    /// defined by its vertex and index buffers.
987    #[cfg(feature = "2d")]
988    pub fn convex_decomposition(vertices: &[RVector], indices: &[[u32; 2]]) -> Self {
989        SharedShape::convex_decomposition(vertices, indices).into()
990    }
991
992    /// Creates a collider shape with a compound shape obtained from the decomposition of a given trimesh
993    /// defined by its vertex and index buffers.
994    #[cfg(feature = "3d")]
995    pub fn convex_decomposition(vertices: &[RVector], indices: &[[u32; 3]]) -> Self {
996        SharedShape::convex_decomposition(vertices, indices).into()
997    }
998
999    /// Creates a collider shape with a compound shape obtained from the decomposition of a given polyline
1000    /// defined by its vertex and index buffers. The given [`VhacdParameters`] are used for configuring
1001    /// the decomposition process.
1002    #[cfg(feature = "2d")]
1003    pub fn convex_decomposition_with_config(
1004        vertices: &[RVector],
1005        indices: &[[u32; 2]],
1006        params: &VhacdParameters,
1007    ) -> Self {
1008        SharedShape::convex_decomposition_with_params(vertices, indices, &params.clone().into())
1009            .into()
1010    }
1011
1012    /// Creates a collider shape with a compound shape obtained from the decomposition of a given trimesh
1013    /// defined by its vertex and index buffers. The given [`VhacdParameters`] are used for configuring
1014    /// the decomposition process.
1015    #[cfg(feature = "3d")]
1016    pub fn convex_decomposition_with_config(
1017        vertices: &[RVector],
1018        indices: &[[u32; 3]],
1019        params: &VhacdParameters,
1020    ) -> Self {
1021        SharedShape::convex_decomposition_with_params(vertices, indices, &params.clone().into())
1022            .into()
1023    }
1024
1025    /// Creates a collider with a [convex polygon](https://en.wikipedia.org/wiki/Convex_polygon) shape obtained after computing
1026    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points.
1027    #[cfg(feature = "2d")]
1028    pub fn convex_hull(points: &[RVector]) -> Option<Self> {
1029        SharedShape::convex_hull(points).map(Into::into)
1030    }
1031
1032    /// Creates a collider with a [convex polyhedron](https://en.wikipedia.org/wiki/Convex_polytope) shape obtained after computing
1033    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points.
1034    #[cfg(feature = "3d")]
1035    pub fn convex_hull(points: &[RVector]) -> Option<Self> {
1036        SharedShape::convex_hull(points).map(Into::into)
1037    }
1038
1039    /// Creates a collider with a [convex polygon](https://en.wikipedia.org/wiki/Convex_polygon) shape **without** computing
1040    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points: convexity of the input is
1041    /// assumed and not checked.
1042    #[cfg(feature = "2d")]
1043    pub fn convex_polyline(points: Vec<RVector>) -> Option<Self> {
1044        SharedShape::convex_polyline(points).map(Into::into)
1045    }
1046
1047    /// Creates a collider shape made of voxels.
1048    ///
1049    /// Each voxel has the size `voxel_size` and grid coordinate given by `grid_coordinates`.
1050    pub fn voxels(voxel_size: Vector, grid_coordinates: &[IVector]) -> Self {
1051        #[cfg(all(feature = "2d", feature = "f64"))]
1052        let grid_coordinates = &grid_coordinates
1053            .iter()
1054            .map(|c| c.as_i64vec2())
1055            .collect::<Vec<_>>();
1056        #[cfg(all(feature = "3d", feature = "f64"))]
1057        let grid_coordinates = &grid_coordinates
1058            .iter()
1059            .map(|c| c.as_i64vec3())
1060            .collect::<Vec<_>>();
1061        let shape = Voxels::new(voxel_size.real(), grid_coordinates);
1062        SharedShape::new(shape).into()
1063    }
1064
1065    /// Creates a collider shape made of voxels.
1066    ///
1067    /// Each voxel has the size `voxel_size` and contains at least one point from `points`.
1068    pub fn voxels_from_points(voxel_size: Vector, points: &[RVector]) -> Self {
1069        SharedShape::voxels_from_points(voxel_size.real(), points).into()
1070    }
1071
1072    /// Creates a voxel collider obtained from the decomposition of the given polyline into voxelized convex parts.
1073    #[cfg(feature = "2d")]
1074    pub fn voxelized_polyline(
1075        vertices: &[RVector],
1076        indices: &[[u32; 2]],
1077        voxel_size: f32,
1078        fill_mode: FillMode,
1079    ) -> Self {
1080        SharedShape::voxelized_mesh(vertices, indices, voxel_size.real(), fill_mode.into()).into()
1081    }
1082
1083    /// Creates a voxel collider obtained from the decomposition of the given trimesh into voxelized convex parts.
1084    #[cfg(feature = "3d")]
1085    pub fn voxelized_trimesh(
1086        vertices: &[RVector],
1087        indices: &[[u32; 3]],
1088        voxel_size: f32,
1089        fill_mode: FillMode,
1090    ) -> Self {
1091        SharedShape::voxelized_mesh(vertices, indices, voxel_size.real(), fill_mode.into()).into()
1092    }
1093
1094    /// Creates a voxel collider obtained from the decomposition of the given `Mesh` into voxelized convex parts.
1095    ///
1096    /// This method is only available if the `collider-from-mesh` feature is enabled.
1097    #[cfg(feature = "collider-from-mesh")]
1098    pub fn voxelized_trimesh_from_mesh(
1099        mesh: &Mesh,
1100        voxel_size: f32,
1101        fill_mode: FillMode,
1102    ) -> Option<Self> {
1103        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1104            SharedShape::voxelized_mesh(&vertices, &indices, voxel_size.real(), fill_mode.into())
1105                .into()
1106        })
1107    }
1108
1109    #[cfg_attr(
1110        feature = "2d",
1111        doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1112    )]
1113    #[cfg_attr(
1114        feature = "3d",
1115        doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1116    )]
1117    pub fn voxelized_convex_decomposition(
1118        vertices: &[RVector],
1119        indices: &[[u32; DIM]],
1120    ) -> Vec<Self> {
1121        Self::voxelized_convex_decomposition_with_config(
1122            vertices,
1123            indices,
1124            &VhacdParameters::default(),
1125        )
1126    }
1127
1128    #[cfg_attr(
1129        feature = "2d",
1130        doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1131    )]
1132    #[cfg_attr(
1133        feature = "3d",
1134        doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1135    )]
1136    pub fn voxelized_convex_decomposition_with_config(
1137        vertices: &[RVector],
1138        indices: &[[u32; DIM]],
1139        parameters: &VhacdParameters,
1140    ) -> Vec<Self> {
1141        SharedShape::voxelized_convex_decomposition_with_params(
1142            vertices,
1143            indices,
1144            &parameters.clone().into(),
1145        )
1146        .into_iter()
1147        .map(|c| c.into())
1148        .collect()
1149    }
1150
1151    /// Creates a collider with a heightfield shape.
1152    ///
1153    /// A 2D heightfield is a segment along the `X` axis, subdivided at regular intervals.
1154    ///
1155    /// `heights` is a list indicating the altitude of each subdivision point, and `scale` controls
1156    /// the scaling factor along each axis.
1157    #[cfg(feature = "2d")]
1158    pub fn heightfield(heights: Vec<Real>, scale: Vector) -> Self {
1159        SharedShape::heightfield(heights, scale.real()).into()
1160    }
1161
1162    /// Creates a collider with a heightfield shape.
1163    ///
1164    /// A 3D heightfield is a rectangle on the `XZ` plane, subdivided in a grid pattern at regular intervals.
1165    ///
1166    /// `heights` is a matrix indicating the altitude of each subdivision point. The number of rows indicates
1167    /// the number of subdivisions along the `X` axis, while the number of columns indicates the number of
1168    /// subdivisions along the `Z` axis.
1169    ///
1170    /// `scale` controls the scaling factor along each axis.
1171    #[cfg(feature = "3d")]
1172    pub fn heightfield(heights: Vec<Vec<Real>>, scale: Vector) -> Self {
1173        let row_count = heights.len();
1174        let column_count = heights[0].len();
1175        let data: Vec<Real> = heights.into_iter().flatten().collect();
1176
1177        assert_eq!(
1178            data.len(),
1179            row_count * column_count,
1180            "Each row in `heights` must have the same amount of points"
1181        );
1182
1183        let heights = parry::utils::Array2::new(row_count, column_count, data);
1184        SharedShape::heightfield(heights, scale.real()).into()
1185    }
1186
1187    /// Creates a collider with a triangle mesh shape from a `Mesh`.
1188    ///
1189    /// Note that the resulting collider will be hollow and have no interior.
1190    /// This makes it more prone to tunneling and other collision issues.
1191    ///
1192    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
1193    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
1194    ///
1195    /// When encountering issues with
1196    /// [Ghost Collisions](https://box2d.org/posts/2020/06/ghost-collisions/) where bodies collide
1197    /// with the internal edges of the trimesh, try setting the flag
1198    /// [`TrimeshFlags::FIX_INTERNAL_EDGES`] via [`Self::trimesh_from_mesh_with_config`], which
1199    /// should fix the issue for edges where none of the adjacent triangles border their neighbor
1200    /// triangles convexly (with an angle less than 180°). Note that this will also not fix the
1201    /// issues with boundary edges between two different colliders.
1202    ///
1203    /// # Example
1204    ///
1205    /// ```
1206    /// use avian3d::prelude::*;
1207    /// use bevy::prelude::*;
1208    ///
1209    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
1210    ///     let mesh = Mesh::from(Cuboid::default());
1211    ///     commands.spawn((
1212    ///         Collider::trimesh_from_mesh(&mesh).unwrap(),
1213    ///         Mesh3d(meshes.add(mesh)),
1214    ///     ));
1215    /// }
1216    /// ```
1217    #[cfg(feature = "collider-from-mesh")]
1218    pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
1219        extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1220            SharedShape::trimesh_with_flags(
1221                vertices,
1222                indices,
1223                TrimeshFlags::MERGE_DUPLICATE_VERTICES.into(),
1224            )
1225            .map(|trimesh| trimesh.into())
1226            .ok()
1227        })
1228    }
1229
1230    /// Creates a collider with a triangle mesh shape from a `Mesh` using the given [`TrimeshFlags`]
1231    /// for controlling the preprocessing.
1232    ///
1233    /// Note that the resulting collider will be hollow and have no interior.
1234    /// This makes it more prone to tunneling and other collision issues.
1235    ///
1236    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
1237    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
1238    ///
1239    /// When encountering issues with
1240    /// [Ghost Collisions](https://box2d.org/posts/2020/06/ghost-collisions/) where bodies collide
1241    /// with the internal edges of the trimesh, try setting the flag
1242    /// [`TrimeshFlags::FIX_INTERNAL_EDGES`], which should fix the issue for edges where none of
1243    /// the adjacent triangles border their neighbor triangles convexly (with an angle less than
1244    /// 180°). Note that this will also not fix the issues with boundary edges between two different
1245    /// colliders.
1246    ///
1247    /// # Example
1248    ///
1249    /// ```
1250    /// use avian3d::prelude::*;
1251    /// use bevy::prelude::*;
1252    ///
1253    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
1254    ///     let mesh = Mesh::from(Cuboid::default());
1255    ///     commands.spawn((
1256    ///         Collider::trimesh_from_mesh_with_config(&mesh, TrimeshFlags::all()).unwrap(),
1257    ///         Mesh3d(meshes.add(mesh)),
1258    ///     ));
1259    /// }
1260    /// ```
1261    #[cfg(feature = "collider-from-mesh")]
1262    pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TrimeshFlags) -> Option<Self> {
1263        extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1264            SharedShape::trimesh_with_flags(vertices, indices, flags.into())
1265                .map(|trimesh| trimesh.into())
1266                .ok()
1267        })
1268    }
1269
1270    /// Creates a collider with a convex polygon shape obtained from the convex hull of a `Mesh`.
1271    ///
1272    /// # Example
1273    ///
1274    /// ```
1275    /// use avian3d::prelude::*;
1276    /// use bevy::prelude::*;
1277    ///
1278    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
1279    ///     let mesh = Mesh::from(Cuboid::default());
1280    ///     commands.spawn((
1281    ///         Collider::convex_hull_from_mesh(&mesh).unwrap(),
1282    ///         Mesh3d(meshes.add(mesh)),
1283    ///     ));
1284    /// }
1285    /// ```
1286    #[cfg(feature = "collider-from-mesh")]
1287    pub fn convex_hull_from_mesh(mesh: &Mesh) -> Option<Self> {
1288        extract_mesh_vertices_indices(mesh)
1289            .and_then(|(vertices, _)| SharedShape::convex_hull(&vertices).map(|shape| shape.into()))
1290    }
1291
1292    /// Creates a compound shape obtained from the decomposition of a `Mesh`.
1293    ///
1294    /// # Example
1295    ///
1296    /// ```
1297    /// use avian3d::prelude::*;
1298    /// use bevy::prelude::*;
1299    ///
1300    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
1301    ///     let mesh = Mesh::from(Cuboid::default());
1302    ///     commands.spawn((
1303    ///         Collider::convex_decomposition_from_mesh(&mesh).unwrap(),
1304    ///         Mesh3d(meshes.add(mesh)),
1305    ///     ));
1306    /// }
1307    /// ```
1308    #[cfg(feature = "collider-from-mesh")]
1309    pub fn convex_decomposition_from_mesh(mesh: &Mesh) -> Option<Self> {
1310        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1311            SharedShape::convex_decomposition(&vertices, &indices).into()
1312        })
1313    }
1314
1315    /// Creates a compound shape obtained from the decomposition of a `Mesh`
1316    /// with the given [`VhacdParameters`] passed to the decomposition algorithm.
1317    ///
1318    /// # Example
1319    ///
1320    /// ```
1321    /// use avian3d::prelude::*;
1322    /// use bevy::prelude::*;
1323    ///
1324    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
1325    ///     let mesh = Mesh::from(Cuboid::default());
1326    ///     let config = VhacdParameters {
1327    ///         convex_hull_approximation: false,
1328    ///         ..default()
1329    ///     };
1330    ///     commands.spawn((
1331    ///         Collider::convex_decomposition_from_mesh_with_config(&mesh, &config).unwrap(),
1332    ///         Mesh3d(meshes.add(mesh)),
1333    ///     ));
1334    /// }
1335    /// ```
1336    #[cfg(feature = "collider-from-mesh")]
1337    pub fn convex_decomposition_from_mesh_with_config(
1338        mesh: &Mesh,
1339        parameters: &VhacdParameters,
1340    ) -> Option<Self> {
1341        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1342            SharedShape::convex_decomposition_with_params(
1343                &vertices,
1344                &indices,
1345                &parameters.clone().into(),
1346            )
1347            .into()
1348        })
1349    }
1350
1351    /// Attempts to create a collider with the given [`ColliderConstructor`].
1352    /// By using this, you can serialize and deserialize the collider's creation method
1353    /// separately from the collider itself via the [`ColliderConstructor`] enum.
1354    ///
1355    #[cfg_attr(
1356        feature = "collider-from-mesh",
1357        doc = "Returns `None` in the following cases:
1358- The given [`ColliderConstructor`] requires a mesh, but none was provided.
1359- Creating the collider from the given [`ColliderConstructor`] failed."
1360    )]
1361    #[cfg_attr(
1362        not(feature = "collider-from-mesh"),
1363        doc = "Returns `None` if creating the collider from the given [`ColliderConstructor`] failed."
1364    )]
1365    pub fn try_from_constructor(
1366        collider_constructor: ColliderConstructor,
1367        #[cfg(feature = "collider-from-mesh")] mesh: Option<&Mesh>,
1368    ) -> Option<Self> {
1369        match collider_constructor {
1370            #[cfg(feature = "2d")]
1371            ColliderConstructor::Circle { radius } => Some(Self::circle(radius)),
1372            #[cfg(feature = "3d")]
1373            ColliderConstructor::Sphere { radius } => Some(Self::sphere(radius)),
1374            #[cfg(feature = "2d")]
1375            ColliderConstructor::Ellipse {
1376                half_width,
1377                half_height,
1378            } => Some(Self::ellipse(half_width, half_height)),
1379            #[cfg(feature = "2d")]
1380            ColliderConstructor::Rectangle { x_length, y_length } => {
1381                Some(Self::rectangle(x_length, y_length))
1382            }
1383            #[cfg(feature = "3d")]
1384            ColliderConstructor::Cuboid {
1385                x_length,
1386                y_length,
1387                z_length,
1388            } => Some(Self::cuboid(x_length, y_length, z_length)),
1389            #[cfg(feature = "2d")]
1390            ColliderConstructor::RoundRectangle {
1391                x_length,
1392                y_length,
1393                border_radius,
1394            } => Some(Self::round_rectangle(x_length, y_length, border_radius)),
1395            #[cfg(feature = "3d")]
1396            ColliderConstructor::RoundCuboid {
1397                x_length,
1398                y_length,
1399                z_length,
1400                border_radius,
1401            } => Some(Self::round_cuboid(
1402                x_length,
1403                y_length,
1404                z_length,
1405                border_radius,
1406            )),
1407            #[cfg(feature = "3d")]
1408            ColliderConstructor::Cylinder { radius, height } => {
1409                Some(Self::cylinder(radius, height))
1410            }
1411            #[cfg(feature = "3d")]
1412            ColliderConstructor::Cone { radius, height } => Some(Self::cone(radius, height)),
1413            ColliderConstructor::Capsule { radius, height } => Some(Self::capsule(radius, height)),
1414            ColliderConstructor::CapsuleEndpoints { radius, a, b } => {
1415                Some(Self::capsule_endpoints(radius, a, b))
1416            }
1417            ColliderConstructor::HalfSpace { outward_normal } => {
1418                Some(Self::half_space(outward_normal))
1419            }
1420            ColliderConstructor::Segment { a, b } => Some(Self::segment(a, b)),
1421            ColliderConstructor::Triangle { a, b, c } => Some(Self::triangle(a, b, c)),
1422            #[cfg(feature = "2d")]
1423            ColliderConstructor::RegularPolygon {
1424                circumradius,
1425                sides,
1426            } => Some(Self::regular_polygon(circumradius, sides)),
1427            ColliderConstructor::Polyline { vertices, indices } => {
1428                Some(Self::polyline(vertices, indices))
1429            }
1430            ColliderConstructor::Trimesh { vertices, indices } => {
1431                Some(Self::trimesh(vertices, indices))
1432            }
1433            ColliderConstructor::TrimeshWithConfig {
1434                vertices,
1435                indices,
1436                flags,
1437            } => Some(Self::trimesh_with_config(vertices, indices, flags)),
1438            #[cfg(feature = "2d")]
1439            ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1440                Some(Self::convex_decomposition(&vertices, &indices))
1441            }
1442            #[cfg(feature = "3d")]
1443            ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1444                Some(Self::convex_decomposition(&vertices, &indices))
1445            }
1446            #[cfg(feature = "2d")]
1447            ColliderConstructor::ConvexDecompositionWithConfig {
1448                vertices,
1449                indices,
1450                params,
1451            } => Some(Self::convex_decomposition_with_config(
1452                &vertices, &indices, &params,
1453            )),
1454            #[cfg(feature = "3d")]
1455            ColliderConstructor::ConvexDecompositionWithConfig {
1456                vertices,
1457                indices,
1458                params,
1459            } => Some(Self::convex_decomposition_with_config(
1460                &vertices, &indices, &params,
1461            )),
1462            #[cfg(feature = "2d")]
1463            ColliderConstructor::ConvexHull { points } => Self::convex_hull(&points),
1464            #[cfg(feature = "3d")]
1465            ColliderConstructor::ConvexHull { points } => Self::convex_hull(&points),
1466            #[cfg(feature = "2d")]
1467            ColliderConstructor::ConvexPolyline { points } => Self::convex_polyline(points),
1468            ColliderConstructor::Voxels {
1469                voxel_size,
1470                grid_coordinates,
1471            } => Some(Self::voxels(voxel_size, &grid_coordinates)),
1472            #[cfg(feature = "2d")]
1473            ColliderConstructor::VoxelizedPolyline {
1474                vertices,
1475                indices,
1476                voxel_size,
1477                fill_mode,
1478            } => Some(Self::voxelized_polyline(
1479                &vertices, &indices, voxel_size, fill_mode,
1480            )),
1481            #[cfg(feature = "3d")]
1482            ColliderConstructor::VoxelizedTrimesh {
1483                vertices,
1484                indices,
1485                voxel_size,
1486                fill_mode,
1487            } => Some(Self::voxelized_trimesh(
1488                &vertices, &indices, voxel_size, fill_mode,
1489            )),
1490            #[cfg(feature = "2d")]
1491            ColliderConstructor::Heightfield { heights, scale } => {
1492                Some(Self::heightfield(heights, scale))
1493            }
1494            #[cfg(feature = "3d")]
1495            ColliderConstructor::Heightfield { heights, scale } => {
1496                Some(Self::heightfield(heights, scale))
1497            }
1498            #[cfg(feature = "collider-from-mesh")]
1499            ColliderConstructor::TrimeshFromMesh => Self::trimesh_from_mesh(mesh?),
1500            #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1501            ColliderConstructor::TrimeshFromMeshWithConfig(flags) => {
1502                Self::trimesh_from_mesh_with_config(mesh?, flags)
1503            }
1504            #[cfg(feature = "collider-from-mesh")]
1505            ColliderConstructor::ConvexDecompositionFromMesh => {
1506                Self::convex_decomposition_from_mesh(mesh?)
1507            }
1508            #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1509            ColliderConstructor::ConvexDecompositionFromMeshWithConfig(params) => {
1510                Self::convex_decomposition_from_mesh_with_config(mesh?, &params)
1511            }
1512            #[cfg(feature = "collider-from-mesh")]
1513            ColliderConstructor::ConvexHullFromMesh => Self::convex_hull_from_mesh(mesh?),
1514            #[cfg(feature = "collider-from-mesh")]
1515            ColliderConstructor::VoxelizedTrimeshFromMesh {
1516                voxel_size,
1517                fill_mode,
1518            } => Self::voxelized_trimesh_from_mesh(mesh?, voxel_size, fill_mode),
1519            ColliderConstructor::Compound(compound_constructors) => {
1520                let shapes: Vec<_> =
1521                    ColliderConstructor::flatten_compound_constructors(compound_constructors)
1522                        .into_iter()
1523                        .filter_map(|(position, rotation, collider_constructor)| {
1524                            Self::try_from_constructor(
1525                                collider_constructor,
1526                                #[cfg(feature = "collider-from-mesh")]
1527                                mesh,
1528                            )
1529                            .map(|collider| (position, rotation, collider))
1530                        })
1531                        .collect();
1532
1533                (!shapes.is_empty()).then(|| Self::compound(shapes))
1534            }
1535        }
1536    }
1537}
1538
1539#[cfg(feature = "collider-from-mesh")]
1540type VerticesIndices = (Vec<RVector>, Vec<[u32; 3]>);
1541
1542#[cfg(feature = "collider-from-mesh")]
1543fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<VerticesIndices> {
1544    let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
1545    let indices = mesh.indices()?;
1546
1547    let vtx: Vec<_> = match vertices {
1548        VertexAttributeValues::Float32(vtx) => Some(
1549            vtx.chunks(3)
1550                .map(|v| [v[0] as Real, v[1] as Real, v[2] as Real].into())
1551                .collect(),
1552        ),
1553        VertexAttributeValues::Float32x3(vtx) => Some(
1554            vtx.iter()
1555                .map(|v| [v[0] as Real, v[1] as Real, v[2] as Real].into())
1556                .collect(),
1557        ),
1558        _ => None,
1559    }?;
1560
1561    let idx = match indices {
1562        Indices::U16(idx) => idx
1563            .as_chunks::<3>()
1564            .0
1565            .iter()
1566            .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
1567            .collect(),
1568        Indices::U32(idx) => idx.as_chunks::<3>().0.to_vec(),
1569    };
1570
1571    Some((vtx, idx))
1572}
1573
1574fn scale_shape(
1575    shape: &SharedShape,
1576    scale: Vector,
1577    num_subdivisions: u32,
1578) -> Result<SharedShape, UnsupportedShape> {
1579    let scale = scale.abs().real();
1580    match shape.as_typed_shape() {
1581        TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(scale.abs()))),
1582        TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
1583            border_radius: s.border_radius,
1584            inner_shape: s.inner_shape.scaled(scale.abs()),
1585        })),
1586        TypedShape::Capsule(c) => match c.scaled(scale.abs(), num_subdivisions) {
1587            None => {
1588                log::error!("Failed to apply scale {} to Capsule shape.", scale);
1589                Ok(SharedShape::ball(0.0))
1590            }
1591            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1592            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1593        },
1594        TypedShape::Ball(b) => {
1595            #[cfg(feature = "2d")]
1596            {
1597                if scale.x == scale.y {
1598                    Ok(SharedShape::ball(b.radius * scale.x.abs()))
1599                } else {
1600                    // A 2D circle becomes an ellipse when scaled non-uniformly.
1601                    Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1602                        half_size: Vec2::splat(b.radius as f32) * scale.f32().abs(),
1603                    })))
1604                }
1605            }
1606            #[cfg(feature = "3d")]
1607            match b.scaled(scale.abs(), num_subdivisions) {
1608                None => {
1609                    log::error!("Failed to apply scale {} to Ball shape.", scale);
1610                    Ok(SharedShape::ball(0.0))
1611                }
1612                Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1613                Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1614            }
1615        }
1616        TypedShape::Segment(s) => Ok(SharedShape::new(s.scaled(scale))),
1617        TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(scale))),
1618        TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
1619            border_radius: t.border_radius,
1620            inner_shape: t.inner_shape.scaled(scale),
1621        })),
1622        TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(scale))),
1623        TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(scale))),
1624        TypedShape::HalfSpace(h) => match h.scaled(scale) {
1625            None => {
1626                log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
1627                Ok(SharedShape::ball(0.0))
1628            }
1629            Some(scaled) => Ok(SharedShape::new(scaled)),
1630        },
1631        TypedShape::Voxels(v) => Ok(SharedShape::new(v.clone().scaled(scale))),
1632        TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(scale))),
1633        #[cfg(feature = "2d")]
1634        TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(scale) {
1635            None => {
1636                log::error!("Failed to apply scale {} to ConvexPolygon shape.", scale);
1637                Ok(SharedShape::ball(0.0))
1638            }
1639            Some(scaled) => Ok(SharedShape::new(scaled)),
1640        },
1641        #[cfg(feature = "2d")]
1642        TypedShape::RoundConvexPolygon(cp) => match cp.inner_shape.clone().scaled(scale) {
1643            None => {
1644                log::error!(
1645                    "Failed to apply scale {} to RoundConvexPolygon shape.",
1646                    scale
1647                );
1648                Ok(SharedShape::ball(0.0))
1649            }
1650            Some(scaled) => Ok(SharedShape::new(RoundShape {
1651                border_radius: cp.border_radius,
1652                inner_shape: scaled,
1653            })),
1654        },
1655        #[cfg(feature = "3d")]
1656        TypedShape::ConvexPolyhedron(cp) => match cp.clone().scaled(scale) {
1657            None => {
1658                log::error!("Failed to apply scale {} to ConvexPolyhedron shape.", scale);
1659                Ok(SharedShape::ball(0.0))
1660            }
1661            Some(scaled) => Ok(SharedShape::new(scaled)),
1662        },
1663        #[cfg(feature = "3d")]
1664        TypedShape::RoundConvexPolyhedron(cp) => match cp.clone().inner_shape.scaled(scale) {
1665            None => {
1666                log::error!(
1667                    "Failed to apply scale {} to RoundConvexPolyhedron shape.",
1668                    scale
1669                );
1670                Ok(SharedShape::ball(0.0))
1671            }
1672            Some(scaled) => Ok(SharedShape::new(RoundShape {
1673                border_radius: cp.border_radius,
1674                inner_shape: scaled,
1675            })),
1676        },
1677        #[cfg(feature = "3d")]
1678        TypedShape::Cylinder(c) => match c.scaled(scale.abs(), num_subdivisions) {
1679            None => {
1680                log::error!("Failed to apply scale {} to Cylinder shape.", scale);
1681                Ok(SharedShape::ball(0.0))
1682            }
1683            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1684            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1685        },
1686        #[cfg(feature = "3d")]
1687        TypedShape::RoundCylinder(c) => match c.inner_shape.scaled(scale.abs(), num_subdivisions) {
1688            None => {
1689                log::error!("Failed to apply scale {} to RoundCylinder shape.", scale);
1690                Ok(SharedShape::ball(0.0))
1691            }
1692            Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1693                border_radius: c.border_radius,
1694                inner_shape: scaled,
1695            })),
1696            Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1697                border_radius: c.border_radius,
1698                inner_shape: scaled,
1699            })),
1700        },
1701        #[cfg(feature = "3d")]
1702        TypedShape::Cone(c) => match c.scaled(scale, num_subdivisions) {
1703            None => {
1704                log::error!("Failed to apply scale {} to Cone shape.", scale);
1705                Ok(SharedShape::ball(0.0))
1706            }
1707            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1708            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1709        },
1710        #[cfg(feature = "3d")]
1711        TypedShape::RoundCone(c) => match c.inner_shape.scaled(scale, num_subdivisions) {
1712            None => {
1713                log::error!("Failed to apply scale {} to RoundCone shape.", scale);
1714                Ok(SharedShape::ball(0.0))
1715            }
1716            Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1717                border_radius: c.border_radius,
1718                inner_shape: scaled,
1719            })),
1720            Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1721                border_radius: c.border_radius,
1722                inner_shape: scaled,
1723            })),
1724        },
1725        TypedShape::Compound(c) => {
1726            let mut scaled = Vec::with_capacity(c.shapes().len());
1727
1728            for (pose, shape) in c.shapes() {
1729                #[cfg(feature = "2d")]
1730                scaled.push((
1731                    make_pose(
1732                        pose.translation * scale,
1733                        Rot::from_sin_cos(pose.rotation.sin().f32(), pose.rotation.cos().f32()),
1734                    ),
1735                    scale_shape(shape, scale.f32(), num_subdivisions)?,
1736                ));
1737                #[cfg(feature = "3d")]
1738                scaled.push((
1739                    make_pose(pose.translation * scale, pose.rotation.f32()),
1740                    scale_shape(shape, scale.f32(), num_subdivisions)?,
1741                ));
1742            }
1743            Ok(SharedShape::compound(scaled))
1744        }
1745        TypedShape::Custom(_shape) => {
1746            #[cfg(feature = "2d")]
1747            {
1748                if let Some(ellipse) = _shape.as_shape::<EllipseColliderShape>() {
1749                    return Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1750                        half_size: ellipse.half_size * scale.f32().abs(),
1751                    })));
1752                }
1753                if let Some(polygon) = _shape.as_shape::<RegularPolygonColliderShape>() {
1754                    if scale.x == scale.y {
1755                        return Ok(SharedShape::new(RegularPolygonColliderShape(
1756                            RegularPolygon::new(
1757                                polygon.circumradius() * scale.x.abs() as f32,
1758                                polygon.sides,
1759                            ),
1760                        )));
1761                    } else {
1762                        let vertices = polygon
1763                            .vertices(0.0)
1764                            .into_iter()
1765                            .map(|v| v.real())
1766                            .collect::<Vec<_>>();
1767
1768                        return scale_shape(
1769                            &SharedShape::convex_hull(&vertices).unwrap(),
1770                            scale.f32(),
1771                            num_subdivisions,
1772                        );
1773                    }
1774                }
1775            }
1776            _shape
1777                .scale_dyn(scale, num_subdivisions)
1778                .map(|scaled| SharedShape(alloc::sync::Arc::from(scaled)))
1779                .ok_or(parry::query::Unsupported)
1780        }
1781    }
1782}
1783
1784#[cfg(all(test, feature = "3d"))]
1785mod tests {
1786    use super::*;
1787    use approx::assert_relative_eq;
1788    use core::f32::consts::PI;
1789
1790    #[test]
1791    fn test_flatten_compound_constructors() {
1792        let input = vec![
1793            (
1794                Position(RVector::new(10.0, 0.0, 0.0)),
1795                Rotation::default(),
1796                ColliderConstructor::Sphere { radius: 1.0 },
1797            ),
1798            (
1799                Position(RVector::new(5.0, 0.0, 0.0)),
1800                Rotation::from(Quat::from_rotation_z(PI / 2.0)),
1801                ColliderConstructor::Compound(vec![
1802                    (
1803                        Position(RVector::new(2.0, 0.0, 0.0)),
1804                        Rotation::from(Quat::from_rotation_y(PI)),
1805                        ColliderConstructor::Compound(vec![(
1806                            Position(RVector::new(1.0, 0.0, 0.0)),
1807                            Rotation::default(),
1808                            ColliderConstructor::Sphere { radius: 0.5 },
1809                        )]),
1810                    ),
1811                    (
1812                        Position(RVector::new(0.0, 3.0, 0.0)),
1813                        Rotation::default(),
1814                        ColliderConstructor::Sphere { radius: 0.25 },
1815                    ),
1816                ]),
1817            ),
1818        ];
1819
1820        let flattened = ColliderConstructor::flatten_compound_constructors(input);
1821        assert_eq!(flattened.len(), 3);
1822
1823        let unchanged_simple_sphere = &flattened[0];
1824        let flattened_grandchild = &flattened[1];
1825        let flattened_sibling = &flattened[2];
1826
1827        // Top level colliders should remain unchanged
1828        assert_eq!(
1829            unchanged_simple_sphere.0,
1830            Position(RVector::new(10.0, 0.0, 0.0))
1831        );
1832        assert_eq!(unchanged_simple_sphere.1, Rotation::default());
1833
1834        // Grandchild local position: (1, 0, 0)
1835        // 1. Apply parent's 180 Y rotation -> (-1, 0, 0)
1836        // 2. Add parent's position (2, 0, 0) -> (1, 0, 0)
1837        // 3. Apply grandparent's 90 Z rotation -> (0, 1, 0)
1838        // 4. Add grandparent's position (5, 0, 0) -> (5, 1, 0)
1839        let expected_grandchild_world_pos = RVector::new(5.0, 1.0, 0.0);
1840        let actual_grandchild_world_pos = flattened_grandchild.0.0;
1841
1842        assert_relative_eq!(
1843            actual_grandchild_world_pos.x,
1844            expected_grandchild_world_pos.x,
1845            epsilon = 1e-6
1846        );
1847        assert_relative_eq!(
1848            actual_grandchild_world_pos.y,
1849            expected_grandchild_world_pos.y,
1850            epsilon = 1e-6
1851        );
1852        assert_relative_eq!(
1853            actual_grandchild_world_pos.z,
1854            expected_grandchild_world_pos.z,
1855            epsilon = 1e-6
1856        );
1857
1858        // Sibling local position: (0, 3, 0)
1859        // 1. Apply parent's 90 Z rotation -> (-3, 0, 0)
1860        // 2. Add parent's position (5, 0, 0) -> (2, 0, 0)
1861        let expected_sibling_world_pos = RVector::new(2.0, 0.0, 0.0);
1862        let actual_sibling_world_pos = flattened_sibling.0.0;
1863
1864        assert_relative_eq!(
1865            actual_sibling_world_pos.x,
1866            expected_sibling_world_pos.x,
1867            epsilon = 1e-6
1868        );
1869        assert_relative_eq!(
1870            actual_sibling_world_pos.y,
1871            expected_sibling_world_pos.y,
1872            epsilon = 1e-6
1873        );
1874        assert_relative_eq!(
1875            actual_sibling_world_pos.z,
1876            expected_sibling_world_pos.z,
1877            epsilon = 1e-6
1878        );
1879    }
1880}