#![allow(clippy::unnecessary_cast)]

pub mod contact_query;

#[cfg(feature = "2d")]
mod primitives2d;
#[cfg(feature = "3d")]
mod primitives3d;

#[cfg(feature = "2d")]
pub use primitives2d::{EllipseColliderShape, RegularPolygonColliderShape};

use super::EnlargedAabb;
use crate::{make_pose, prelude::*};
#[cfg(feature = "collider-from-mesh")]
use bevy::mesh::{Indices, VertexAttributeValues};
use bevy::{log, prelude::*};
use contact_query::UnsupportedShape;
use itertools::Either;
use parry::shape::{RoundShape, SharedShape, TypedShape, Voxels};

impl<T: IntoCollider<Collider>> From<T> for Collider {
    fn from(value: T) -> Self {
        value.collider()
    }
}

/// Parameters controlling the VHACD convex decomposition.
///
/// See <https://github.com/Unity-Technologies/VHACD#parameters> for details.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(PartialEq, Debug)]
pub struct VhacdParameters {
    /// Maximum concavity.
    ///
    /// Default: 0.1 (in 2D), 0.01 (in 3D).
    /// Valid range `[0.0, 1.0]`.
    pub concavity: Scalar,
    /// Controls the bias toward clipping along symmetry planes.
    ///
    /// Default: 0.05.
    /// Valid Range: `[0.0, 1.0]`.
    pub alpha: Scalar,
    /// Controls the bias toward clipping along revolution planes.
    ///
    /// Default: 0.05.
    /// Valid Range: `[0.0, 1.0]`.
    pub beta: Scalar,
    /// Resolution used during the voxelization stage.
    ///
    /// Default: 256 (in 2D), 64 (in 3D).
    pub resolution: u32,
    /// Controls the granularity of the search for the best
    /// clipping plane during the decomposition.
    ///
    /// Default: 4
    pub plane_downsampling: u32,
    /// Controls the precision of the convex-hull generation
    /// process during the clipping plane selection stage.
    ///
    /// Default: 4
    pub convex_hull_downsampling: u32,
    /// Controls the way the input mesh or polyline is being
    /// voxelized.
    ///
    /// Default: `FillMode::FloodFill { detect_cavities: false, detect_self_intersections: false }`
    pub fill_mode: FillMode,
    /// Controls whether the convex-hull should be approximated during the decomposition stage.
    /// Setting this to `true` increases performances with a slight degradation of the decomposition
    /// quality.
    ///
    /// Default: true
    pub convex_hull_approximation: bool,
    /// Controls the max number of convex-hull generated by the convex decomposition.
    ///
    /// Default: 1024
    pub max_convex_hulls: u32,
}

impl Default for VhacdParameters {
    fn default() -> Self {
        Self {
            #[cfg(feature = "3d")]
            resolution: 64,
            #[cfg(feature = "3d")]
            concavity: 0.01,
            #[cfg(feature = "2d")]
            resolution: 256,
            #[cfg(feature = "2d")]
            concavity: 0.1,
            plane_downsampling: 4,
            convex_hull_downsampling: 4,
            alpha: 0.05,
            beta: 0.05,
            convex_hull_approximation: true,
            max_convex_hulls: 1024,
            fill_mode: FillMode::FloodFill {
                detect_cavities: false,
                #[cfg(feature = "2d")]
                detect_self_intersections: false,
            },
        }
    }
}

impl From<VhacdParameters> for parry::transformation::vhacd::VHACDParameters {
    fn from(value: VhacdParameters) -> Self {
        Self {
            concavity: value.concavity,
            alpha: value.alpha,
            beta: value.beta,
            resolution: value.resolution,
            plane_downsampling: value.plane_downsampling,
            convex_hull_downsampling: value.convex_hull_downsampling,
            fill_mode: value.fill_mode.into(),
            convex_hull_approximation: value.convex_hull_approximation,
            max_convex_hulls: value.max_convex_hulls,
        }
    }
}

/// Controls how the voxelization determines which voxel needs
/// to be considered empty, and which ones will be considered full.
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Hash, PartialEq, Debug)]
pub enum FillMode {
    /// Only consider full the voxels intersecting the surface of the
    /// shape being voxelized.
    SurfaceOnly,
    /// Use a flood-fill technique to consider fill the voxels intersecting
    /// the surface of the shape being voxelized, as well as all the voxels
    /// bounded of them.
    FloodFill {
        /// Detects holes inside of a solid contour.
        detect_cavities: bool,
        /// Attempts to properly handle self-intersections.
        #[cfg(feature = "2d")]
        detect_self_intersections: bool,
    },
}

impl From<FillMode> for parry::transformation::voxelization::FillMode {
    fn from(value: FillMode) -> Self {
        match value {
            FillMode::SurfaceOnly => Self::SurfaceOnly,
            FillMode::FloodFill {
                detect_cavities,
                #[cfg(feature = "2d")]
                detect_self_intersections,
            } => Self::FloodFill {
                detect_cavities,
                #[cfg(feature = "2d")]
                detect_self_intersections,
            },
        }
    }
}

/// Flags used for the preprocessing of a triangle mesh collider.
#[repr(transparent)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
#[reflect(opaque, Hash, PartialEq, Debug)]
pub struct TrimeshFlags(u8);

bitflags::bitflags! {
    impl TrimeshFlags: u8 {
        /// If set, the half-edge topology of the trimesh will be computed if possible.
        const HALF_EDGE_TOPOLOGY = 0b0000_0001;
        /// If set, the half-edge topology and connected components of the trimesh will be computed if possible.
        ///
        /// Because of the way it is currently implemented, connected components can only be computed on
        /// a mesh where the half-edge topology computation succeeds. It will no longer be the case in the
        /// future once we decouple the computations.
        const CONNECTED_COMPONENTS = 0b0000_0010;
        /// If set, any triangle that results in a failing half-hedge topology computation will be deleted.
        const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
        /// If set, the trimesh will be assumed to be oriented (with outward normals).
        ///
        /// The pseudo-normals of its vertices and edges will be computed.
        const ORIENTED = 0b0000_1000;
        /// If set, the duplicate vertices of the trimesh will be merged.
        ///
        /// Two vertices with the exact same coordinates will share the same entry on the
        /// vertex buffer and the index buffer is adjusted accordingly.
        const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
        /// If set, the triangles sharing two vertices with identical index values will be removed.
        ///
        /// Because of the way it is currently implemented, this methods implies that duplicate
        /// vertices will be merged. It will no longer be the case in the future once we decouple
        /// the computations.
        const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
        /// If set, two triangles sharing three vertices with identical index values (in any order) will be removed.
        ///
        /// Because of the way it is currently implemented, this methods implies that duplicate
        /// vertices will be merged. It will no longer be the case in the future once we decouple
        /// the computations.
        const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
        /// If set, a special treatment will be applied to contact manifold calculation to eliminate
        /// or fix contacts normals that could lead to incorrect bumps in physics simulation
        /// (especially on flat surfaces).
        ///
        /// This is achieved by taking into account adjacent triangle normals when computing contact
        /// points for a given triangle.
        const FIX_INTERNAL_EDGES = 0b1000_0000 | Self::ORIENTED.bits() | Self::MERGE_DUPLICATE_VERTICES.bits();
    }
}

impl From<TrimeshFlags> for parry::shape::TriMeshFlags {
    fn from(value: TrimeshFlags) -> Self {
        Self::from_bits(value.bits().into()).unwrap()
    }
}

/// An error indicating an inconsistency when building a triangle mesh collider.
pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError;

/// A collider used for detecting collisions and generating contacts.
///
/// # Creation
///
/// `Collider` has tons of methods for creating colliders of various shapes:
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// # fn setup(mut commands: Commands) {
/// // Create a ball collider with a given radius
#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
/// // Create a capsule collider with a given radius and height
/// commands.spawn(Collider::capsule(0.5, 2.0));
/// # }
/// ```
///
/// Colliders on their own only detect contacts and generate
/// [collision events](crate::collision#collision-events).
/// To make colliders apply contact forces, they have to be attached
/// to [rigid bodies](RigidBody):
///
/// ```
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
/// use bevy::prelude::*;
///
/// // Spawn a dynamic body that falls onto a static platform
/// fn setup(mut commands: Commands) {
///     commands.spawn((
///         RigidBody::Dynamic,
#[cfg_attr(feature = "2d", doc = "        Collider::circle(0.5),")]
#[cfg_attr(feature = "3d", doc = "        Collider::sphere(0.5),")]
///         Transform::from_xyz(0.0, 2.0, 0.0),
///     ));
#[cfg_attr(
    feature = "2d",
    doc = "    commands.spawn((RigidBody::Static, Collider::rectangle(5.0, 0.5)));"
)]
#[cfg_attr(
    feature = "3d",
    doc = "    commands.spawn((RigidBody::Static, Collider::cuboid(5.0, 0.5, 5.0)));"
)]
/// }
/// ```
///
/// Colliders can be further configured using various components like [`Friction`], [`Restitution`],
/// [`Sensor`], [`CollisionLayers`], [`CollisionMargin`], and [`ColliderDensity`].
///
/// If you need to specify the shape of the collider statically, use [`ColliderConstructor`] and build your collider
/// with the [`Collider::try_from_constructor`] method.
/// This can also be done automatically by simply placing the [`ColliderConstructor`] on an entity.
///
#[cfg_attr(
    feature = "3d",
    doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
)]
///
/// ## Multiple Colliders
///
/// It can often be useful to attach multiple colliders to the same rigid body.
///
/// This can be done in two ways. Either use [`Collider::compound`] to have one collider that consists of many
/// shapes, or for more control, spawn several collider entities as the children of a rigid body:
///
/// ```
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
/// use bevy::prelude::*;
///
/// fn setup(mut commands: Commands) {
///     // Spawn a rigid body with one collider on the same entity and two as children
///     commands
#[cfg_attr(
    feature = "2d",
    doc = "        .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
)]
#[cfg_attr(
    feature = "3d",
    doc = "        .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
)]
///         .with_children(|children| {
///             // Spawn the child colliders positioned relative to the rigid body
#[cfg_attr(
    feature = "2d",
    doc = "            children.spawn((Collider::circle(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
            children.spawn((Collider::circle(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
)]
#[cfg_attr(
    feature = "3d",
    doc = "            children.spawn((Collider::sphere(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
            children.spawn((Collider::sphere(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
)]
///         });
/// }
/// ```
///
/// Colliders can be arbitrarily nested and transformed relative to the parent.
/// The rigid body that a collider is attached to can be accessed using the [`ColliderOf`] component.
///
/// The benefit of using separate entities for the colliders is that each collider can have its own
/// [friction](Friction), [restitution](Restitution), [collision layers](CollisionLayers),
/// and other configuration options, and they send separate [collision events](crate::collision#collision-events).
///
/// # See More
///
/// - [Rigid bodies](RigidBody)
/// - [Density](ColliderDensity)
/// - [Friction] and [restitution](Restitution) (bounciness)
/// - [Collision layers](CollisionLayers)
/// - [Sensors](Sensor)
/// - [Collision margins for adding extra thickness to colliders](CollisionMargin)
#[cfg_attr(
    feature = "3d",
    doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
)]
/// - [Get colliding entities](CollidingEntities)
/// - [Collision events](crate::collision#collision-events)
/// - [Accessing collision data](Collisions)
/// - [Filtering and modifying contacts with hooks](CollisionHooks)
/// - [Manual contact queries](contact_query)
///
/// # Advanced Usage
///
/// Internally, `Collider` uses the shapes provided by `parry`. If you want to create a collider
/// using these shapes, you can simply use `Collider::from(SharedShape::some_method())`.
///
/// To get a reference to the internal [`SharedShape`], you can use the [`Collider::shape()`]
/// or [`Collider::shape_scaled()`] methods.
///
/// `Collider` is currently not `Reflect`. If you need to reflect it, you can use [`ColliderConstructor`] as a workaround.
#[derive(Clone, Component, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[require(
    ColliderMarker,
    ColliderAabb,
    CollisionLayers,
    EnlargedAabb,
    ColliderDensity,
    ColliderMassProperties
)]
pub struct Collider {
    /// The raw unscaled collider shape.
    shape: SharedShape,
    /// The scaled version of the collider shape.
    ///
    /// If the scale is `Vector::ONE`, this will be `None` and `unscaled_shape`
    /// will be used instead.
    scaled_shape: SharedShape,
    /// The global scale used for the collider shape.
    scale: Vector,
}

impl From<SharedShape> for Collider {
    fn from(value: SharedShape) -> Self {
        Self {
            shape: value.clone(),
            scaled_shape: value,
            scale: Vector::ONE,
        }
    }
}

impl Default for Collider {
    fn default() -> Self {
        #[cfg(feature = "2d")]
        {
            Self::rectangle(0.5, 0.5)
        }
        #[cfg(feature = "3d")]
        {
            Self::cuboid(0.5, 0.5, 0.5)
        }
    }
}

impl AnyCollider for Collider {
    type Context = ();

    fn aabb_with_context(
        &self,
        position: Vector,
        rotation: impl Into<Rotation>,
        _: AabbContext<Self::Context>,
    ) -> ColliderAabb {
        let aabb = self
            .shape_scaled()
            .compute_aabb(&make_pose(position, rotation));
        ColliderAabb {
            min: aabb.mins,
            max: aabb.maxs,
        }
    }

    fn contact_manifolds_with_context(
        &self,
        other: &Self,
        position1: Vector,
        rotation1: impl Into<Rotation>,
        position2: Vector,
        rotation2: impl Into<Rotation>,
        prediction_distance: Scalar,
        manifolds: &mut Vec<ContactManifold>,
        _: ContactManifoldContext<Self::Context>,
    ) {
        contact_query::contact_manifolds(
            self,
            position1,
            rotation1,
            other,
            position2,
            rotation2,
            prediction_distance,
            manifolds,
        )
    }
}

// TODO: `bevy_heavy` supports computing the individual mass properties efficiently for Bevy's primitive shapes,
//       but Parry doesn't support it for its own shapes, so we have to compute all mass properties in each method :(
#[cfg(feature = "2d")]
impl ComputeMassProperties for Collider {
    fn mass(&self, density: f32) -> f32 {
        let props = self.shape_scaled().mass_properties(density as Scalar);
        props.mass() as f32
    }

    fn unit_angular_inertia(&self) -> f32 {
        self.angular_inertia(1.0)
    }

    fn angular_inertia(&self, mass: f32) -> f32 {
        let props = self.shape_scaled().mass_properties(mass as Scalar);
        props.principal_inertia() as f32
    }

    fn center_of_mass(&self) -> Vec2 {
        let props = self.shape_scaled().mass_properties(1.0);
        props.local_com.f32()
    }

    fn mass_properties(&self, density: f32) -> MassProperties {
        let props = self.shape_scaled().mass_properties(density as Scalar);

        MassProperties {
            mass: props.mass() as f32,
            #[cfg(feature = "2d")]
            angular_inertia: props.principal_inertia() as f32,
            #[cfg(feature = "3d")]
            principal_angular_inertia: props.principal_inertia().f32(),
            #[cfg(feature = "3d")]
            local_inertial_frame: props.principal_inertia_local_frame.f32(),
            center_of_mass: props.local_com.f32(),
        }
    }
}

#[cfg(feature = "3d")]
impl ComputeMassProperties for Collider {
    fn mass(&self, density: f32) -> f32 {
        let props = self.shape_scaled().mass_properties(density as Scalar);
        props.mass() as f32
    }

    fn unit_principal_angular_inertia(&self) -> Vec3 {
        self.principal_angular_inertia(1.0)
    }

    fn principal_angular_inertia(&self, mass: f32) -> Vec3 {
        let props = self.shape_scaled().mass_properties(mass as Scalar);
        props.principal_inertia().f32()
    }

    fn local_inertial_frame(&self) -> Quat {
        let props = self.shape_scaled().mass_properties(1.0);
        props.principal_inertia_local_frame.f32()
    }

    fn center_of_mass(&self) -> Vec3 {
        let props = self.shape_scaled().mass_properties(1.0);
        props.local_com.f32()
    }

    fn mass_properties(&self, density: f32) -> MassProperties {
        let props = self.shape_scaled().mass_properties(density as Scalar);

        MassProperties {
            mass: props.mass() as f32,
            #[cfg(feature = "2d")]
            angular_inertia: props.principal_inertia() as f32,
            #[cfg(feature = "3d")]
            principal_angular_inertia: props.principal_inertia().f32(),
            #[cfg(feature = "3d")]
            local_inertial_frame: props.principal_inertia_local_frame.f32(),
            center_of_mass: props.local_com.f32(),
        }
    }
}

impl ScalableCollider for Collider {
    fn scale(&self) -> Vector {
        self.scale()
    }

    fn set_scale(&mut self, scale: Vector, detail: u32) {
        self.set_scale(scale, detail)
    }
}

impl Collider {
    /// Returns the raw unscaled shape of the collider.
    pub fn shape(&self) -> &SharedShape {
        &self.shape
    }

    /// Returns a mutable reference to the raw unscaled shape of the collider.
    pub fn shape_mut(&mut self) -> &mut SharedShape {
        &mut self.shape
    }

    /// Returns the shape of the collider with the scale from its `GlobalTransform` applied.
    pub fn shape_scaled(&self) -> &SharedShape {
        &self.scaled_shape
    }

    /// Sets the unscaled shape of the collider. The collider's scale will be applied to this shape.
    pub fn set_shape(&mut self, shape: SharedShape) {
        self.shape = shape;

        // TODO: The number of subdivisions probably shouldn't be hard-coded
        if let Ok(scaled) = scale_shape(&self.shape, self.scale, 10) {
            self.scaled_shape = scaled;
        } else {
            log::error!("Failed to create convex hull for scaled collider.");
        }
    }

    /// Returns the global scale of the collider.
    pub fn scale(&self) -> Vector {
        self.scale
    }

    /// Set the global scaling factor of this shape.
    ///
    /// If the scaling factor is not uniform, and the scaled shape can’t be
    /// represented as a supported shape, the shape is approximated as
    /// a convex polygon or polyhedron using `num_subdivisions`.
    ///
    /// For example, if a ball was scaled to an ellipse, the new shape would be approximated.
    pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
        if scale == self.scale {
            return;
        }

        if scale == Vector::ONE {
            // Trivial case.
            self.scaled_shape = self.shape.clone();
            self.scale = Vector::ONE;
            return;
        }

        if let Ok(scaled) = scale_shape(&self.shape, scale, num_subdivisions) {
            self.scaled_shape = scaled;
            self.scale = scale;
        } else {
            log::error!("Failed to create convex hull for scaled collider.");
        }
    }

    /// Projects the given `point` onto `self` transformed by `translation` and `rotation`.
    /// The returned tuple contains the projected point and whether it is inside the collider.
    ///
    /// If `solid` is true and the given `point` is inside of the collider, the projection will be at the point.
    /// Otherwise, the collider will be treated as hollow, and the projection will be at the collider's boundary.
    pub fn project_point(
        &self,
        translation: impl Into<Position>,
        rotation: impl Into<Rotation>,
        point: Vector,
        solid: bool,
    ) -> (Vector, bool) {
        let projection =
            self.shape_scaled()
                .project_point(&make_pose(translation, rotation), point, solid);
        (projection.point, projection.is_inside)
    }

    /// Computes the minimum distance between the given `point` and `self` transformed by `translation` and `rotation`.
    ///
    /// If `solid` is true and the given `point` is inside of the collider, the returned distance will be `0.0`.
    /// Otherwise, the collider will be treated as hollow, and the distance will be the distance
    /// to the collider's boundary.
    pub fn distance_to_point(
        &self,
        translation: impl Into<Position>,
        rotation: impl Into<Rotation>,
        point: Vector,
        solid: bool,
    ) -> Scalar {
        self.shape_scaled()
            .distance_to_point(&make_pose(translation, rotation), point, solid)
    }

    /// Tests whether the given `point` is inside of `self` transformed by `translation` and `rotation`.
    pub fn contains_point(
        &self,
        translation: impl Into<Position>,
        rotation: impl Into<Rotation>,
        point: Vector,
    ) -> bool {
        self.shape_scaled()
            .contains_point(&make_pose(translation, rotation), point)
    }

    /// Computes the distance and normal between the given ray and `self`
    /// transformed by `translation` and `rotation`.
    ///
    /// The returned tuple is in the format `(distance, normal)`.
    ///
    /// # Arguments
    ///
    /// - `ray_origin`: Where the ray is cast from.
    /// - `ray_direction`: What direction the ray is cast in.
    /// - `max_distance`: The maximum distance the ray can travel.
    /// - `solid`: If true and the ray origin is inside of a collider, the hit point will be the ray origin itself.
    ///   Otherwise, the collider will be treated as hollow, and the hit point will be at the collider's boundary.
    pub fn cast_ray(
        &self,
        translation: impl Into<Position>,
        rotation: impl Into<Rotation>,
        ray_origin: Vector,
        ray_direction: Vector,
        max_distance: Scalar,
        solid: bool,
    ) -> Option<(Scalar, Vector)> {
        let hit = self.shape_scaled().cast_ray_and_get_normal(
            &make_pose(translation, rotation),
            &parry::query::Ray::new(ray_origin, ray_direction),
            max_distance,
            solid,
        );
        hit.map(|hit| (hit.time_of_impact, hit.normal))
    }

    /// Tests whether the given ray intersects `self` transformed by `translation` and `rotation`.
    ///
    /// # Arguments
    ///
    /// - `ray_origin`: Where the ray is cast from.
    /// - `ray_direction`: What direction the ray is cast in.
    /// - `max_distance`: The maximum distance the ray can travel.
    pub fn intersects_ray(
        &self,
        translation: impl Into<Position>,
        rotation: impl Into<Rotation>,
        ray_origin: Vector,
        ray_direction: Vector,
        max_distance: Scalar,
    ) -> bool {
        self.shape_scaled().intersects_ray(
            &make_pose(translation, rotation),
            &parry::query::Ray::new(ray_origin, ray_direction),
            max_distance,
        )
    }

    /// Creates a collider with a compound shape defined by a given vector of colliders with a position and a rotation.
    ///
    /// Especially for dynamic rigid bodies, compound shape colliders should be preferred over triangle meshes and polylines,
    /// because convex shapes typically provide more reliable results.
    ///
    /// If you want to create a compound shape from a 3D triangle mesh or 2D polyline, consider using the
    /// [`Collider::convex_decomposition`] method.
    pub fn compound(
        shapes: Vec<(
            impl Into<Position>,
            impl Into<Rotation>,
            impl Into<Collider>,
        )>,
    ) -> Self {
        let shapes = shapes
            .into_iter()
            .map(|(p, r, c)| {
                (
                    make_pose(*p.into(), r.into()),
                    c.into().shape_scaled().clone(),
                )
            })
            .collect::<Vec<_>>();
        SharedShape::compound(shapes).into()
    }

    /// Creates a collider with a circle shape defined by its radius.
    #[cfg(feature = "2d")]
    pub fn circle(radius: Scalar) -> Self {
        SharedShape::ball(radius).into()
    }

    /// Creates a collider with a sphere shape defined by its radius.
    #[cfg(feature = "3d")]
    pub fn sphere(radius: Scalar) -> Self {
        SharedShape::ball(radius).into()
    }

    /// Creates a collider with an ellipse shape defined by a half-width and half-height.
    #[cfg(feature = "2d")]
    pub fn ellipse(half_width: Scalar, half_height: Scalar) -> Self {
        SharedShape::new(EllipseColliderShape(Ellipse::new(
            half_width as f32,
            half_height as f32,
        )))
        .into()
    }

    /// Creates a collider with a rectangle shape defined by its extents.
    #[cfg(feature = "2d")]
    pub fn rectangle(x_length: Scalar, y_length: Scalar) -> Self {
        SharedShape::cuboid(x_length * 0.5, y_length * 0.5).into()
    }

    /// Creates a collider with a cuboid shape defined by its extents.
    #[cfg(feature = "3d")]
    pub fn cuboid(x_length: Scalar, y_length: Scalar, z_length: Scalar) -> Self {
        SharedShape::cuboid(x_length * 0.5, y_length * 0.5, z_length * 0.5).into()
    }

    /// Creates a collider with a rectangle shape defined by its extents and rounded corners.
    #[cfg(feature = "2d")]
    pub fn round_rectangle(x_length: Scalar, y_length: Scalar, border_radius: Scalar) -> Self {
        SharedShape::round_cuboid(x_length * 0.5, y_length * 0.5, border_radius).into()
    }

    /// Creates a collider with a cuboid shape defined by its extents and rounded corners.
    #[cfg(feature = "3d")]
    pub fn round_cuboid(
        x_length: Scalar,
        y_length: Scalar,
        z_length: Scalar,
        border_radius: Scalar,
    ) -> Self {
        SharedShape::round_cuboid(
            x_length * 0.5,
            y_length * 0.5,
            z_length * 0.5,
            border_radius,
        )
        .into()
    }

    /// Creates a collider with a cylinder shape defined by its radius
    /// on the `XZ` plane and its height along the `Y` axis.
    #[cfg(feature = "3d")]
    pub fn cylinder(radius: Scalar, height: Scalar) -> Self {
        SharedShape::cylinder(height * 0.5, radius).into()
    }

    /// Creates a collider with a cone shape defined by the radius of its base
    /// on the `XZ` plane and its height along the `Y` axis.
    #[cfg(feature = "3d")]
    pub fn cone(radius: Scalar, height: Scalar) -> Self {
        SharedShape::cone(height * 0.5, radius).into()
    }

    /// Creates a collider with a capsule shape defined by its radius
    /// and its height along the `Y` axis, excluding the hemispheres.
    pub fn capsule(radius: Scalar, length: Scalar) -> Self {
        SharedShape::capsule(
            Vector::Y * length * 0.5,
            Vector::NEG_Y * length * 0.5,
            radius,
        )
        .into()
    }

    /// Creates a collider with a capsule shape defined by its radius and endpoints `a` and `b`.
    pub fn capsule_endpoints(radius: Scalar, a: Vector, b: Vector) -> Self {
        SharedShape::capsule(a, b, radius).into()
    }

    /// Creates a collider with a [half-space](https://en.wikipedia.org/wiki/Half-space_(geometry)) shape
    /// defined by the outward normal of its planar boundary.
    pub fn half_space(outward_normal: Vector) -> Self {
        SharedShape::halfspace(outward_normal.normalize_or_zero()).into()
    }

    /// Creates a collider with a segment shape defined by its endpoints `a` and `b`.
    pub fn segment(a: Vector, b: Vector) -> Self {
        SharedShape::segment(a, b).into()
    }

    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
    ///
    /// If the triangle is oriented clockwise, it will be reversed to be counterclockwise
    /// by swapping `b` and `c`. This is needed for collision detection.
    ///
    /// If you know that the given points produce a counterclockwise triangle,
    /// consider using [`Collider::triangle_unchecked`] instead.
    #[cfg(feature = "2d")]
    pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
        let mut triangle = parry::shape::Triangle::new(a, b, c);

        // Make sure the triangle is counterclockwise. This is needed for collision detection.
        if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
            triangle.reverse();
        }

        SharedShape::new(triangle).into()
    }

    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
    ///
    /// The orientation of the triangle is assumed to be counterclockwise.
    /// This is needed for collision detection.
    ///
    /// If you are unsure about the orientation of the triangle, consider using [`Collider::triangle`] instead.
    #[cfg(feature = "2d")]
    pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
        SharedShape::triangle(a, b, c).into()
    }

    /// Creates a collider with a triangle shape defined by its points `a`, `b`, and `c`.
    #[cfg(feature = "3d")]
    pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
        SharedShape::triangle(a, b, c).into()
    }

    /// Creates a collider with a regular polygon shape defined by the circumradius and the number of sides.
    #[cfg(feature = "2d")]
    pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
        RegularPolygon::new(circumradius, sides).collider()
    }

    /// Creates a collider with a polyline shape defined by its vertices and optionally an index buffer.
    pub fn polyline(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
        SharedShape::polyline(vertices, indices).into()
    }

    /// Creates a collider with a triangle mesh shape defined by its vertex and index buffers.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// # Panics
    ///
    /// Panics if the given vertex and index buffers do not contain any triangles,
    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
    pub fn trimesh(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
        Self::try_trimesh(vertices, indices)
            .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
    }

    /// Tries to create a collider with a triangle mesh shape defined by its vertex and index buffers.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// # Errors
    ///
    /// Returns a [`TrimeshBuilderError`] if the given vertex and index buffers do not contain any triangles,
    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
    pub fn try_trimesh(
        vertices: Vec<Vector>,
        indices: Vec<[u32; 3]>,
    ) -> Result<Self, TrimeshBuilderError> {
        SharedShape::trimesh(vertices, indices).map(|trimesh| trimesh.into())
    }

    /// Creates a collider with a triangle mesh shape defined by its vertex and index buffers
    /// and flags controlling the preprocessing.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// # Panics
    ///
    /// Panics if after preprocessing the given vertex and index buffers do not contain any triangles,
    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
    pub fn trimesh_with_config(
        vertices: Vec<Vector>,
        indices: Vec<[u32; 3]>,
        flags: TrimeshFlags,
    ) -> Self {
        Self::try_trimesh_with_config(vertices, indices, flags)
            .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
    }

    /// Tries to create a collider with a triangle mesh shape defined by its vertex and index buffers
    /// and flags controlling the preprocessing.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// # Errors
    ///
    /// Returns a [`TrimeshBuilderError`] if after preprocessing the given vertex and index buffers do not contain any triangles,
    /// there are duplicate vertices, or if at least two adjacent triangles have opposite orientations.
    pub fn try_trimesh_with_config(
        vertices: Vec<Vector>,
        indices: Vec<[u32; 3]>,
        flags: TrimeshFlags,
    ) -> Result<Self, TrimeshBuilderError> {
        SharedShape::trimesh_with_flags(vertices, indices, flags.into())
            .map(|trimesh| trimesh.into())
    }

    /// Creates a collider shape with a compound shape obtained from the decomposition of a given polyline
    /// defined by its vertex and index buffers.
    #[cfg(feature = "2d")]
    pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 2]>) -> Self {
        SharedShape::convex_decomposition(&vertices, &indices).into()
    }

    /// Creates a collider shape with a compound shape obtained from the decomposition of a given trimesh
    /// defined by its vertex and index buffers.
    #[cfg(feature = "3d")]
    pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
        SharedShape::convex_decomposition(&vertices, &indices).into()
    }

    /// Creates a collider shape with a compound shape obtained from the decomposition of a given polyline
    /// defined by its vertex and index buffers. The given [`VhacdParameters`] are used for configuring
    /// the decomposition process.
    #[cfg(feature = "2d")]
    pub fn convex_decomposition_with_config(
        vertices: Vec<Vector>,
        indices: Vec<[u32; 2]>,
        params: &VhacdParameters,
    ) -> Self {
        SharedShape::convex_decomposition_with_params(&vertices, &indices, &params.clone().into())
            .into()
    }

    /// Creates a collider shape with a compound shape obtained from the decomposition of a given trimesh
    /// defined by its vertex and index buffers. The given [`VhacdParameters`] are used for configuring
    /// the decomposition process.
    #[cfg(feature = "3d")]
    pub fn convex_decomposition_with_config(
        vertices: Vec<Vector>,
        indices: Vec<[u32; 3]>,
        params: VhacdParameters,
    ) -> Self {
        SharedShape::convex_decomposition_with_params(&vertices, &indices, &params.clone().into())
            .into()
    }

    /// Creates a collider with a [convex polygon](https://en.wikipedia.org/wiki/Convex_polygon) shape obtained after computing
    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points.
    #[cfg(feature = "2d")]
    pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
        SharedShape::convex_hull(&points).map(Into::into)
    }

    /// Creates a collider with a [convex polyhedron](https://en.wikipedia.org/wiki/Convex_polytope) shape obtained after computing
    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points.
    #[cfg(feature = "3d")]
    pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
        SharedShape::convex_hull(&points).map(Into::into)
    }

    /// Creates a collider with a [convex polygon](https://en.wikipedia.org/wiki/Convex_polygon) shape **without** computing
    /// the [convex hull](https://en.wikipedia.org/wiki/Convex_hull) of the given points: convexity of the input is
    /// assumed and not checked.
    #[cfg(feature = "2d")]
    pub fn convex_polyline(points: Vec<Vector>) -> Option<Self> {
        SharedShape::convex_polyline(points).map(Into::into)
    }

    /// Creates a collider shape made of voxels.
    ///
    /// Each voxel has the size `voxel_size` and grid coordinate given by `grid_coordinates`.
    pub fn voxels(voxel_size: Vector, grid_coordinates: &[IVector]) -> Self {
        #[cfg(all(feature = "2d", feature = "f64"))]
        let grid_coordinates = &grid_coordinates
            .iter()
            .map(|c| c.as_i64vec2())
            .collect::<Vec<_>>();
        #[cfg(all(feature = "3d", feature = "f64"))]
        let grid_coordinates = &grid_coordinates
            .iter()
            .map(|c| c.as_i64vec3())
            .collect::<Vec<_>>();
        let shape = Voxels::new(voxel_size, grid_coordinates);
        SharedShape::new(shape).into()
    }

    /// Creates a collider shape made of voxels.
    ///
    /// Each voxel has the size `voxel_size` and contains at least one point from `points`.
    pub fn voxels_from_points(voxel_size: Vector, points: &[Vector]) -> Self {
        SharedShape::voxels_from_points(voxel_size, points).into()
    }

    /// Creates a voxel collider obtained from the decomposition of the given polyline into voxelized convex parts.
    #[cfg(feature = "2d")]
    pub fn voxelized_polyline(
        vertices: &[Vector],
        indices: &[[u32; 2]],
        voxel_size: Scalar,
        fill_mode: FillMode,
    ) -> Self {
        SharedShape::voxelized_mesh(vertices, indices, voxel_size, fill_mode.into()).into()
    }

    /// Creates a voxel collider obtained from the decomposition of the given trimesh into voxelized convex parts.
    #[cfg(feature = "3d")]
    pub fn voxelized_trimesh(
        vertices: &[Vector],
        indices: &[[u32; 3]],
        voxel_size: Scalar,
        fill_mode: FillMode,
    ) -> Self {
        SharedShape::voxelized_mesh(vertices, indices, voxel_size, fill_mode.into()).into()
    }

    /// Creates a voxel collider obtained from the decomposition of the given `Mesh` into voxelized convex parts.
    ///
    /// This method is only available if the `collider-from-mesh` feature is enabled.
    #[cfg(feature = "collider-from-mesh")]
    pub fn voxelized_trimesh_from_mesh(
        mesh: &Mesh,
        voxel_size: Scalar,
        fill_mode: FillMode,
    ) -> Option<Self> {
        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
            SharedShape::voxelized_mesh(&vertices, &indices, voxel_size, fill_mode.into()).into()
        })
    }

    #[cfg_attr(
        feature = "2d",
        doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
    )]
    #[cfg_attr(
        feature = "3d",
        doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
    )]
    pub fn voxelized_convex_decomposition(
        vertices: &[Vector],
        indices: &[[u32; DIM]],
    ) -> Vec<Self> {
        Self::voxelized_convex_decomposition_with_config(
            vertices,
            indices,
            &VhacdParameters::default(),
        )
    }

    #[cfg_attr(
        feature = "2d",
        doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
    )]
    #[cfg_attr(
        feature = "3d",
        doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
    )]
    pub fn voxelized_convex_decomposition_with_config(
        vertices: &[Vector],
        indices: &[[u32; DIM]],
        parameters: &VhacdParameters,
    ) -> Vec<Self> {
        SharedShape::voxelized_convex_decomposition_with_params(
            vertices,
            indices,
            &parameters.clone().into(),
        )
        .into_iter()
        .map(|c| c.into())
        .collect()
    }

    /// Creates a collider with a heightfield shape.
    ///
    /// A 2D heightfield is a segment along the `X` axis, subdivided at regular intervals.
    ///
    /// `heights` is a list indicating the altitude of each subdivision point, and `scale` controls
    /// the scaling factor along each axis.
    #[cfg(feature = "2d")]
    pub fn heightfield(heights: Vec<Scalar>, scale: Vector) -> Self {
        SharedShape::heightfield(heights, scale).into()
    }

    /// Creates a collider with a heightfield shape.
    ///
    /// A 3D heightfield is a rectangle on the `XZ` plane, subdivided in a grid pattern at regular intervals.
    ///
    /// `heights` is a matrix indicating the altitude of each subdivision point. The number of rows indicates
    /// the number of subdivisions along the `X` axis, while the number of columns indicates the number of
    /// subdivisions along the `Z` axis.
    ///
    /// `scale` controls the scaling factor along each axis.
    #[cfg(feature = "3d")]
    pub fn heightfield(heights: Vec<Vec<Scalar>>, scale: Vector) -> Self {
        let row_count = heights.len();
        let column_count = heights[0].len();
        let data: Vec<Scalar> = heights.into_iter().flatten().collect();

        assert_eq!(
            data.len(),
            row_count * column_count,
            "Each row in `heights` must have the same amount of points"
        );

        let heights = parry::utils::Array2::new(row_count, column_count, data);
        SharedShape::heightfield(heights, scale).into()
    }

    /// Creates a collider with a triangle mesh shape from a `Mesh`.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// When encountering issues with
    /// [Ghost Collisions](https://box2d.org/posts/2020/06/ghost-collisions/) where bodies collide
    /// with the internal edges of the trimesh, try setting the flag
    /// [`TrimeshFlags::FIX_INTERNAL_EDGES`] via [`Self::trimesh_from_mesh_with_config`], which
    /// should fix the issue for edges where none of the adjacent triangles border their neighbor
    /// triangles convexly (with an angle less than 180°). Note that this will also not fix the
    /// issues with boundary edges between two different colliders.
    ///
    /// # Example
    ///
    /// ```
    /// use avian3d::prelude::*;
    /// use bevy::prelude::*;
    ///
    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
    ///     let mesh = Mesh::from(Cuboid::default());
    ///     commands.spawn((
    ///         Collider::trimesh_from_mesh(&mesh).unwrap(),
    ///         Mesh3d(meshes.add(mesh)),
    ///     ));
    /// }
    /// ```
    #[cfg(feature = "collider-from-mesh")]
    pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
        extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
            SharedShape::trimesh_with_flags(
                vertices,
                indices,
                TrimeshFlags::MERGE_DUPLICATE_VERTICES.into(),
            )
            .map(|trimesh| trimesh.into())
            .ok()
        })
    }

    /// Creates a collider with a triangle mesh shape from a `Mesh` using the given [`TrimeshFlags`]
    /// for controlling the preprocessing.
    ///
    /// Note that the resulting collider will be hollow and have no interior.
    /// This makes it more prone to tunneling and other collision issues.
    ///
    /// The [`CollisionMargin`] component can be used to add thickness to the shape if needed.
    /// For thin shapes like triangle meshes, it can help improve collision stability and performance.
    ///
    /// When encountering issues with
    /// [Ghost Collisions](https://box2d.org/posts/2020/06/ghost-collisions/) where bodies collide
    /// with the internal edges of the trimesh, try setting the flag
    /// [`TrimeshFlags::FIX_INTERNAL_EDGES`], which should fix the issue for edges where none of
    /// the adjacent triangles border their neighbor triangles convexly (with an angle less than
    /// 180°). Note that this will also not fix the issues with boundary edges between two different
    /// colliders.
    ///
    /// # Example
    ///
    /// ```
    /// use avian3d::prelude::*;
    /// use bevy::prelude::*;
    ///
    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
    ///     let mesh = Mesh::from(Cuboid::default());
    ///     commands.spawn((
    ///         Collider::trimesh_from_mesh_with_config(&mesh, TrimeshFlags::all()).unwrap(),
    ///         Mesh3d(meshes.add(mesh)),
    ///     ));
    /// }
    /// ```
    #[cfg(feature = "collider-from-mesh")]
    pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TrimeshFlags) -> Option<Self> {
        extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
            SharedShape::trimesh_with_flags(vertices, indices, flags.into())
                .map(|trimesh| trimesh.into())
                .ok()
        })
    }

    /// Creates a collider with a convex polygon shape obtained from the convex hull of a `Mesh`.
    ///
    /// # Example
    ///
    /// ```
    /// use avian3d::prelude::*;
    /// use bevy::prelude::*;
    ///
    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
    ///     let mesh = Mesh::from(Cuboid::default());
    ///     commands.spawn((
    ///         Collider::convex_hull_from_mesh(&mesh).unwrap(),
    ///         Mesh3d(meshes.add(mesh)),
    ///     ));
    /// }
    /// ```
    #[cfg(feature = "collider-from-mesh")]
    pub fn convex_hull_from_mesh(mesh: &Mesh) -> Option<Self> {
        extract_mesh_vertices_indices(mesh)
            .and_then(|(vertices, _)| SharedShape::convex_hull(&vertices).map(|shape| shape.into()))
    }

    /// Creates a compound shape obtained from the decomposition of a `Mesh`.
    ///
    /// # Example
    ///
    /// ```
    /// use avian3d::prelude::*;
    /// use bevy::prelude::*;
    ///
    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
    ///     let mesh = Mesh::from(Cuboid::default());
    ///     commands.spawn((
    ///         Collider::convex_decomposition_from_mesh(&mesh).unwrap(),
    ///         Mesh3d(meshes.add(mesh)),
    ///     ));
    /// }
    /// ```
    #[cfg(feature = "collider-from-mesh")]
    pub fn convex_decomposition_from_mesh(mesh: &Mesh) -> Option<Self> {
        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
            SharedShape::convex_decomposition(&vertices, &indices).into()
        })
    }

    /// Creates a compound shape obtained from the decomposition of a `Mesh`
    /// with the given [`VhacdParameters`] passed to the decomposition algorithm.
    ///
    /// # Example
    ///
    /// ```
    /// use avian3d::prelude::*;
    /// use bevy::prelude::*;
    ///
    /// fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
    ///     let mesh = Mesh::from(Cuboid::default());
    ///     let config = VhacdParameters {
    ///         convex_hull_approximation: false,
    ///         ..default()
    ///     };
    ///     commands.spawn((
    ///         Collider::convex_decomposition_from_mesh_with_config(&mesh, &config).unwrap(),
    ///         Mesh3d(meshes.add(mesh)),
    ///     ));
    /// }
    /// ```
    #[cfg(feature = "collider-from-mesh")]
    pub fn convex_decomposition_from_mesh_with_config(
        mesh: &Mesh,
        parameters: &VhacdParameters,
    ) -> Option<Self> {
        extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
            SharedShape::convex_decomposition_with_params(
                &vertices,
                &indices,
                &parameters.clone().into(),
            )
            .into()
        })
    }

    /// Attempts to create a collider with the given [`ColliderConstructor`].
    /// By using this, you can serialize and deserialize the collider's creation method
    /// separately from the collider itself via the [`ColliderConstructor`] enum.
    ///
    #[cfg_attr(
        feature = "collider-from-mesh",
        doc = "Returns `None` in the following cases:
- The given [`ColliderConstructor`] requires a mesh, but none was provided.
- Creating the collider from the given [`ColliderConstructor`] failed."
    )]
    #[cfg_attr(
        not(feature = "collider-from-mesh"),
        doc = "Returns `None` if creating the collider from the given [`ColliderConstructor`] failed."
    )]
    pub fn try_from_constructor(
        collider_constructor: ColliderConstructor,
        #[cfg(feature = "collider-from-mesh")] mesh: Option<&Mesh>,
    ) -> Option<Self> {
        match collider_constructor {
            #[cfg(feature = "2d")]
            ColliderConstructor::Circle { radius } => Some(Self::circle(radius)),
            #[cfg(feature = "3d")]
            ColliderConstructor::Sphere { radius } => Some(Self::sphere(radius)),
            #[cfg(feature = "2d")]
            ColliderConstructor::Ellipse {
                half_width,
                half_height,
            } => Some(Self::ellipse(half_width, half_height)),
            #[cfg(feature = "2d")]
            ColliderConstructor::Rectangle { x_length, y_length } => {
                Some(Self::rectangle(x_length, y_length))
            }
            #[cfg(feature = "3d")]
            ColliderConstructor::Cuboid {
                x_length,
                y_length,
                z_length,
            } => Some(Self::cuboid(x_length, y_length, z_length)),
            #[cfg(feature = "2d")]
            ColliderConstructor::RoundRectangle {
                x_length,
                y_length,
                border_radius,
            } => Some(Self::round_rectangle(x_length, y_length, border_radius)),
            #[cfg(feature = "3d")]
            ColliderConstructor::RoundCuboid {
                x_length,
                y_length,
                z_length,
                border_radius,
            } => Some(Self::round_cuboid(
                x_length,
                y_length,
                z_length,
                border_radius,
            )),
            #[cfg(feature = "3d")]
            ColliderConstructor::Cylinder { radius, height } => {
                Some(Self::cylinder(radius, height))
            }
            #[cfg(feature = "3d")]
            ColliderConstructor::Cone { radius, height } => Some(Self::cone(radius, height)),
            ColliderConstructor::Capsule { radius, height } => Some(Self::capsule(radius, height)),
            ColliderConstructor::CapsuleEndpoints { radius, a, b } => {
                Some(Self::capsule_endpoints(radius, a, b))
            }
            ColliderConstructor::HalfSpace { outward_normal } => {
                Some(Self::half_space(outward_normal))
            }
            ColliderConstructor::Segment { a, b } => Some(Self::segment(a, b)),
            ColliderConstructor::Triangle { a, b, c } => Some(Self::triangle(a, b, c)),
            #[cfg(feature = "2d")]
            ColliderConstructor::RegularPolygon {
                circumradius,
                sides,
            } => Some(Self::regular_polygon(circumradius, sides)),
            ColliderConstructor::Polyline { vertices, indices } => {
                Some(Self::polyline(vertices, indices))
            }
            ColliderConstructor::Trimesh { vertices, indices } => {
                Some(Self::trimesh(vertices, indices))
            }
            ColliderConstructor::TrimeshWithConfig {
                vertices,
                indices,
                flags,
            } => Some(Self::trimesh_with_config(vertices, indices, flags)),
            #[cfg(feature = "2d")]
            ColliderConstructor::ConvexDecomposition { vertices, indices } => {
                Some(Self::convex_decomposition(vertices, indices))
            }
            #[cfg(feature = "3d")]
            ColliderConstructor::ConvexDecomposition { vertices, indices } => {
                Some(Self::convex_decomposition(vertices, indices))
            }
            #[cfg(feature = "2d")]
            ColliderConstructor::ConvexDecompositionWithConfig {
                vertices,
                indices,
                params,
            } => Some(Self::convex_decomposition_with_config(
                vertices, indices, &params,
            )),
            #[cfg(feature = "3d")]
            ColliderConstructor::ConvexDecompositionWithConfig {
                vertices,
                indices,
                params,
            } => Some(Self::convex_decomposition_with_config(
                vertices, indices, params,
            )),
            #[cfg(feature = "2d")]
            ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
            #[cfg(feature = "3d")]
            ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
            #[cfg(feature = "2d")]
            ColliderConstructor::ConvexPolyline { points } => Self::convex_polyline(points),
            ColliderConstructor::Voxels {
                voxel_size,
                grid_coordinates,
            } => Some(Self::voxels(voxel_size, &grid_coordinates)),
            #[cfg(feature = "2d")]
            ColliderConstructor::VoxelizedPolyline {
                vertices,
                indices,
                voxel_size,
                fill_mode,
            } => Some(Self::voxelized_polyline(
                &vertices, &indices, voxel_size, fill_mode,
            )),
            #[cfg(feature = "3d")]
            ColliderConstructor::VoxelizedTrimesh {
                vertices,
                indices,
                voxel_size,
                fill_mode,
            } => Some(Self::voxelized_trimesh(
                &vertices, &indices, voxel_size, fill_mode,
            )),
            #[cfg(feature = "2d")]
            ColliderConstructor::Heightfield { heights, scale } => {
                Some(Self::heightfield(heights, scale))
            }
            #[cfg(feature = "3d")]
            ColliderConstructor::Heightfield { heights, scale } => {
                Some(Self::heightfield(heights, scale))
            }
            #[cfg(feature = "collider-from-mesh")]
            ColliderConstructor::TrimeshFromMesh => Self::trimesh_from_mesh(mesh?),
            #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
            ColliderConstructor::TrimeshFromMeshWithConfig(flags) => {
                Self::trimesh_from_mesh_with_config(mesh?, flags)
            }
            #[cfg(feature = "collider-from-mesh")]
            ColliderConstructor::ConvexDecompositionFromMesh => {
                Self::convex_decomposition_from_mesh(mesh?)
            }
            #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
            ColliderConstructor::ConvexDecompositionFromMeshWithConfig(params) => {
                Self::convex_decomposition_from_mesh_with_config(mesh?, &params)
            }
            #[cfg(feature = "collider-from-mesh")]
            ColliderConstructor::ConvexHullFromMesh => Self::convex_hull_from_mesh(mesh?),
            #[cfg(feature = "collider-from-mesh")]
            ColliderConstructor::VoxelizedTrimeshFromMesh {
                voxel_size,
                fill_mode,
            } => Self::voxelized_trimesh_from_mesh(mesh?, voxel_size, fill_mode),
            ColliderConstructor::Compound(compound_constructors) => {
                let shapes: Vec<_> =
                    ColliderConstructor::flatten_compound_constructors(compound_constructors)
                        .into_iter()
                        .filter_map(|(position, rotation, collider_constructor)| {
                            Self::try_from_constructor(
                                collider_constructor,
                                #[cfg(feature = "collider-from-mesh")]
                                mesh,
                            )
                            .map(|collider| (position, rotation, collider))
                        })
                        .collect();

                (!shapes.is_empty()).then(|| Self::compound(shapes))
            }
        }
    }
}

#[cfg(feature = "collider-from-mesh")]
type VerticesIndices = (Vec<Vector>, Vec<[u32; 3]>);

#[cfg(feature = "collider-from-mesh")]
fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<VerticesIndices> {
    let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
    let indices = mesh.indices()?;

    let vtx: Vec<_> = match vertices {
        VertexAttributeValues::Float32(vtx) => Some(
            vtx.chunks(3)
                .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
                .collect(),
        ),
        VertexAttributeValues::Float32x3(vtx) => Some(
            vtx.iter()
                .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
                .collect(),
        ),
        _ => None,
    }?;

    let idx = match indices {
        Indices::U16(idx) => idx
            .chunks_exact(3)
            .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
            .collect(),
        Indices::U32(idx) => idx.chunks_exact(3).map(|i| [i[0], i[1], i[2]]).collect(),
    };

    Some((vtx, idx))
}

fn scale_shape(
    shape: &SharedShape,
    scale: Vector,
    num_subdivisions: u32,
) -> Result<SharedShape, UnsupportedShape> {
    let scale = scale.abs();
    match shape.as_typed_shape() {
        TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(scale.abs()))),
        TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
            border_radius: s.border_radius,
            inner_shape: s.inner_shape.scaled(scale.abs()),
        })),
        TypedShape::Capsule(c) => match c.scaled(scale.abs(), num_subdivisions) {
            None => {
                log::error!("Failed to apply scale {} to Capsule shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
        },
        TypedShape::Ball(b) => {
            #[cfg(feature = "2d")]
            {
                if scale.x == scale.y {
                    Ok(SharedShape::ball(b.radius * scale.x.abs()))
                } else {
                    // A 2D circle becomes an ellipse when scaled non-uniformly.
                    Ok(SharedShape::new(EllipseColliderShape(Ellipse {
                        half_size: Vec2::splat(b.radius as f32) * scale.f32().abs(),
                    })))
                }
            }
            #[cfg(feature = "3d")]
            match b.scaled(scale.abs(), num_subdivisions) {
                None => {
                    log::error!("Failed to apply scale {} to Ball shape.", scale);
                    Ok(SharedShape::ball(0.0))
                }
                Some(Either::Left(b)) => Ok(SharedShape::new(b)),
                Some(Either::Right(b)) => Ok(SharedShape::new(b)),
            }
        }
        TypedShape::Segment(s) => Ok(SharedShape::new(s.scaled(scale))),
        TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(scale))),
        TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
            border_radius: t.border_radius,
            inner_shape: t.inner_shape.scaled(scale),
        })),
        TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(scale))),
        TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(scale))),
        TypedShape::HalfSpace(h) => match h.scaled(scale) {
            None => {
                log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(scaled) => Ok(SharedShape::new(scaled)),
        },
        TypedShape::Voxels(v) => Ok(SharedShape::new(v.clone().scaled(scale))),
        TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(scale))),
        #[cfg(feature = "2d")]
        TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(scale) {
            None => {
                log::error!("Failed to apply scale {} to ConvexPolygon shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(scaled) => Ok(SharedShape::new(scaled)),
        },
        #[cfg(feature = "2d")]
        TypedShape::RoundConvexPolygon(cp) => match cp.inner_shape.clone().scaled(scale) {
            None => {
                log::error!(
                    "Failed to apply scale {} to RoundConvexPolygon shape.",
                    scale
                );
                Ok(SharedShape::ball(0.0))
            }
            Some(scaled) => Ok(SharedShape::new(RoundShape {
                border_radius: cp.border_radius,
                inner_shape: scaled,
            })),
        },
        #[cfg(feature = "3d")]
        TypedShape::ConvexPolyhedron(cp) => match cp.clone().scaled(scale) {
            None => {
                log::error!("Failed to apply scale {} to ConvexPolyhedron shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(scaled) => Ok(SharedShape::new(scaled)),
        },
        #[cfg(feature = "3d")]
        TypedShape::RoundConvexPolyhedron(cp) => match cp.clone().inner_shape.scaled(scale) {
            None => {
                log::error!(
                    "Failed to apply scale {} to RoundConvexPolyhedron shape.",
                    scale
                );
                Ok(SharedShape::ball(0.0))
            }
            Some(scaled) => Ok(SharedShape::new(RoundShape {
                border_radius: cp.border_radius,
                inner_shape: scaled,
            })),
        },
        #[cfg(feature = "3d")]
        TypedShape::Cylinder(c) => match c.scaled(scale.abs(), num_subdivisions) {
            None => {
                log::error!("Failed to apply scale {} to Cylinder shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
        },
        #[cfg(feature = "3d")]
        TypedShape::RoundCylinder(c) => match c.inner_shape.scaled(scale.abs(), num_subdivisions) {
            None => {
                log::error!("Failed to apply scale {} to RoundCylinder shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
                border_radius: c.border_radius,
                inner_shape: scaled,
            })),
            Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
                border_radius: c.border_radius,
                inner_shape: scaled,
            })),
        },
        #[cfg(feature = "3d")]
        TypedShape::Cone(c) => match c.scaled(scale, num_subdivisions) {
            None => {
                log::error!("Failed to apply scale {} to Cone shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(Either::Left(b)) => Ok(SharedShape::new(b)),
            Some(Either::Right(b)) => Ok(SharedShape::new(b)),
        },
        #[cfg(feature = "3d")]
        TypedShape::RoundCone(c) => match c.inner_shape.scaled(scale, num_subdivisions) {
            None => {
                log::error!("Failed to apply scale {} to RoundCone shape.", scale);
                Ok(SharedShape::ball(0.0))
            }
            Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
                border_radius: c.border_radius,
                inner_shape: scaled,
            })),
            Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
                border_radius: c.border_radius,
                inner_shape: scaled,
            })),
        },
        TypedShape::Compound(c) => {
            let mut scaled = Vec::with_capacity(c.shapes().len());

            for (pose, shape) in c.shapes() {
                #[cfg(feature = "2d")]
                scaled.push((
                    make_pose(
                        pose.translation * scale,
                        Rotation::radians(pose.rotation.angle()),
                    ),
                    scale_shape(shape, scale, num_subdivisions)?,
                ));
                #[cfg(feature = "3d")]
                scaled.push((
                    make_pose(pose.translation * scale, pose.rotation),
                    scale_shape(shape, scale, num_subdivisions)?,
                ));
            }
            Ok(SharedShape::compound(scaled))
        }
        TypedShape::Custom(_shape) => {
            #[cfg(feature = "2d")]
            {
                if let Some(ellipse) = _shape.as_shape::<EllipseColliderShape>() {
                    return Ok(SharedShape::new(EllipseColliderShape(Ellipse {
                        half_size: ellipse.half_size * scale.f32().abs(),
                    })));
                }
                if let Some(polygon) = _shape.as_shape::<RegularPolygonColliderShape>() {
                    if scale.x == scale.y {
                        return Ok(SharedShape::new(RegularPolygonColliderShape(
                            RegularPolygon::new(
                                polygon.circumradius() * scale.x.abs() as f32,
                                polygon.sides,
                            ),
                        )));
                    } else {
                        let vertices = polygon
                            .vertices(0.0)
                            .into_iter()
                            .map(|v| v.adjust_precision())
                            .collect::<Vec<_>>();

                        return scale_shape(
                            &SharedShape::convex_hull(&vertices).unwrap(),
                            scale,
                            num_subdivisions,
                        );
                    }
                }
            }
            Err(parry::query::Unsupported)
        }
    }
}

#[cfg(all(test, feature = "3d"))]
mod tests {
    use super::*;
    use approx::assert_relative_eq;

    #[test]
    fn test_flatten_compound_constructors() {
        let input = vec![
            (
                Position(Vector::new(10.0, 0.0, 0.0)),
                Rotation::default(),
                ColliderConstructor::Sphere { radius: 1.0 },
            ),
            (
                Position(Vector::new(5.0, 0.0, 0.0)),
                Rotation::from(Quaternion::from_rotation_z(PI / 2.0)),
                ColliderConstructor::Compound(vec![
                    (
                        Position(Vector::new(2.0, 0.0, 0.0)),
                        Rotation::from(Quaternion::from_rotation_y(PI)),
                        ColliderConstructor::Compound(vec![(
                            Position(Vector::new(1.0, 0.0, 0.0)),
                            Rotation::default(),
                            ColliderConstructor::Sphere { radius: 0.5 },
                        )]),
                    ),
                    (
                        Position(Vector::new(0.0, 3.0, 0.0)),
                        Rotation::default(),
                        ColliderConstructor::Sphere { radius: 0.25 },
                    ),
                ]),
            ),
        ];

        let flattened = ColliderConstructor::flatten_compound_constructors(input);
        assert_eq!(flattened.len(), 3);

        let unchanged_simple_sphere = &flattened[0];
        let flattened_grandchild = &flattened[1];
        let flattened_sibling = &flattened[2];

        // Top level colliders should remain unchanged
        assert_eq!(
            unchanged_simple_sphere.0,
            Position(Vector::new(10.0, 0.0, 0.0))
        );
        assert_eq!(unchanged_simple_sphere.1, Rotation::default());

        // Grandchild local position: (1, 0, 0)
        // 1. Apply parent's 180 Y rotation -> (-1, 0, 0)
        // 2. Add parent's position (2, 0, 0) -> (1, 0, 0)
        // 3. Apply grandparent's 90 Z rotation -> (0, 1, 0)
        // 4. Add grandparent's position (5, 0, 0) -> (5, 1, 0)
        let expected_grandchild_world_pos = Vector::new(5.0, 1.0, 0.0);
        let actual_grandchild_world_pos = flattened_grandchild.0.0;

        assert_relative_eq!(
            actual_grandchild_world_pos.x,
            expected_grandchild_world_pos.x,
            epsilon = 1e-6
        );
        assert_relative_eq!(
            actual_grandchild_world_pos.y,
            expected_grandchild_world_pos.y,
            epsilon = 1e-6
        );
        assert_relative_eq!(
            actual_grandchild_world_pos.z,
            expected_grandchild_world_pos.z,
            epsilon = 1e-6
        );

        // Sibling local position: (0, 3, 0)
        // 1. Apply parent's 90 Z rotation -> (-3, 0, 0)
        // 2. Add parent's position (5, 0, 0) -> (2, 0, 0)
        let expected_sibling_world_pos = Vector::new(2.0, 0.0, 0.0);
        let actual_sibling_world_pos = flattened_sibling.0.0;

        assert_relative_eq!(
            actual_sibling_world_pos.x,
            expected_sibling_world_pos.x,
            epsilon = 1e-6
        );
        assert_relative_eq!(
            actual_sibling_world_pos.y,
            expected_sibling_world_pos.y,
            epsilon = 1e-6
        );
        assert_relative_eq!(
            actual_sibling_world_pos.z,
            expected_sibling_world_pos.z,
            epsilon = 1e-6
        );
    }
}
