#![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()
}
}
#[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 {
pub concavity: Scalar,
pub alpha: Scalar,
pub beta: Scalar,
pub resolution: u32,
pub plane_downsampling: u32,
pub convex_hull_downsampling: u32,
pub fill_mode: FillMode,
pub convex_hull_approximation: bool,
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,
}
}
}
#[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 {
SurfaceOnly,
FloodFill {
detect_cavities: bool,
#[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,
},
}
}
}
#[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 {
const HALF_EDGE_TOPOLOGY = 0b0000_0001;
const CONNECTED_COMPONENTS = 0b0000_0010;
const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
const ORIENTED = 0b0000_1000;
const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
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()
}
}
pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError;
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")]
#[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")]
#[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)));"
)]
#[cfg_attr(
feature = "3d",
doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
)]
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = " .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
)]
#[cfg_attr(
feature = "3d",
doc = " .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
)]
#[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)));"
)]
#[cfg_attr(
feature = "3d",
doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
)]
#[derive(Clone, Component, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[require(
ColliderMarker,
ColliderAabb,
CollisionLayers,
EnlargedAabb,
ColliderDensity,
ColliderMassProperties
)]
pub struct Collider {
shape: SharedShape,
scaled_shape: SharedShape,
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,
)
}
}
#[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 {
pub fn shape(&self) -> &SharedShape {
&self.shape
}
pub fn shape_mut(&mut self) -> &mut SharedShape {
&mut self.shape
}
pub fn shape_scaled(&self) -> &SharedShape {
&self.scaled_shape
}
pub fn set_shape(&mut self, shape: SharedShape) {
self.shape = shape;
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.");
}
}
pub fn scale(&self) -> Vector {
self.scale
}
pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
if scale == self.scale {
return;
}
if scale == Vector::ONE {
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.");
}
}
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)
}
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)
}
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)
}
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))
}
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,
)
}
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()
}
#[cfg(feature = "2d")]
pub fn circle(radius: Scalar) -> Self {
SharedShape::ball(radius).into()
}
#[cfg(feature = "3d")]
pub fn sphere(radius: Scalar) -> Self {
SharedShape::ball(radius).into()
}
#[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()
}
#[cfg(feature = "2d")]
pub fn rectangle(x_length: Scalar, y_length: Scalar) -> Self {
SharedShape::cuboid(x_length * 0.5, y_length * 0.5).into()
}
#[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()
}
#[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()
}
#[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()
}
#[cfg(feature = "3d")]
pub fn cylinder(radius: Scalar, height: Scalar) -> Self {
SharedShape::cylinder(height * 0.5, radius).into()
}
#[cfg(feature = "3d")]
pub fn cone(radius: Scalar, height: Scalar) -> Self {
SharedShape::cone(height * 0.5, radius).into()
}
pub fn capsule(radius: Scalar, length: Scalar) -> Self {
SharedShape::capsule(
Vector::Y * length * 0.5,
Vector::NEG_Y * length * 0.5,
radius,
)
.into()
}
pub fn capsule_endpoints(radius: Scalar, a: Vector, b: Vector) -> Self {
SharedShape::capsule(a, b, radius).into()
}
pub fn half_space(outward_normal: Vector) -> Self {
SharedShape::halfspace(outward_normal.normalize_or_zero()).into()
}
pub fn segment(a: Vector, b: Vector) -> Self {
SharedShape::segment(a, b).into()
}
#[cfg(feature = "2d")]
pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
let mut triangle = parry::shape::Triangle::new(a, b, c);
if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
triangle.reverse();
}
SharedShape::new(triangle).into()
}
#[cfg(feature = "2d")]
pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
SharedShape::triangle(a, b, c).into()
}
#[cfg(feature = "3d")]
pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
SharedShape::triangle(a, b, c).into()
}
#[cfg(feature = "2d")]
pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
RegularPolygon::new(circumradius, sides).collider()
}
pub fn polyline(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
SharedShape::polyline(vertices, indices).into()
}
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:?}"))
}
pub fn try_trimesh(
vertices: Vec<Vector>,
indices: Vec<[u32; 3]>,
) -> Result<Self, TrimeshBuilderError> {
SharedShape::trimesh(vertices, indices).map(|trimesh| trimesh.into())
}
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:?}"))
}
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())
}
#[cfg(feature = "2d")]
pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 2]>) -> Self {
SharedShape::convex_decomposition(&vertices, &indices).into()
}
#[cfg(feature = "3d")]
pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
SharedShape::convex_decomposition(&vertices, &indices).into()
}
#[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, ¶ms.clone().into())
.into()
}
#[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, ¶ms.clone().into())
.into()
}
#[cfg(feature = "2d")]
pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
SharedShape::convex_hull(&points).map(Into::into)
}
#[cfg(feature = "3d")]
pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
SharedShape::convex_hull(&points).map(Into::into)
}
#[cfg(feature = "2d")]
pub fn convex_polyline(points: Vec<Vector>) -> Option<Self> {
SharedShape::convex_polyline(points).map(Into::into)
}
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()
}
pub fn voxels_from_points(voxel_size: Vector, points: &[Vector]) -> Self {
SharedShape::voxels_from_points(voxel_size, points).into()
}
#[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()
}
#[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()
}
#[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,
¶meters.clone().into(),
)
.into_iter()
.map(|c| c.into())
.collect()
}
#[cfg(feature = "2d")]
pub fn heightfield(heights: Vec<Scalar>, scale: Vector) -> Self {
SharedShape::heightfield(heights, scale).into()
}
#[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()
}
#[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()
})
}
#[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()
})
}
#[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()))
}
#[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()
})
}
#[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,
¶meters.clone().into(),
)
.into()
})
}
#[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, ¶ms,
)),
#[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?, ¶ms)
}
#[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 {
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];
assert_eq!(
unchanged_simple_sphere.0,
Position(Vector::new(10.0, 0.0, 0.0))
);
assert_eq!(unchanged_simple_sphere.1, Rotation::default());
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
);
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
);
}
}