#![allow(clippy::unnecessary_cast)]
use crate::{prelude::*, utils::make_isometry};
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
use bevy::render::mesh::{Indices, VertexAttributeValues};
use bevy::{log, prelude::*};
use collision::contact_query::UnsupportedShape;
use itertools::Either;
use parry::shape::{RoundShape, SharedShape, TypedShape};
#[cfg(feature = "2d")]
mod primitives2d;
#[cfg(feature = "3d")]
mod primitives3d;
#[cfg(feature = "2d")]
pub(crate) use primitives2d::{EllipseWrapper, RegularPolygonWrapper};
impl<T: IntoCollider<Collider>> From<T> for Collider {
fn from(value: T) -> Self {
value.collider()
}
}
pub type VHACDParameters = parry::transformation::vhacd::VHACDParameters;
pub type TriMeshFlags = parry::shape::TriMeshFlags;
#[cfg_attr(feature = "2d", doc = "# use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use bevy_xpbd_3d::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 bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::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 from meshes and scenes. See [`AsyncCollider`] and [`AsyncSceneCollider`]."
)]
#[cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::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),
TransformBundle::from_transform(Transform::from_xyz(2.0, 0.0, 0.0)),
));
children.spawn((
Collider::circle(0.5),
TransformBundle::from_transform(Transform::from_xyz(-2.0, 0.0, 0.0)),
));"
)]
#[cfg_attr(
feature = "3d",
doc = " children.spawn((
Collider::sphere(0.5),
TransformBundle::from_transform(Transform::from_xyz(2.0, 0.0, 0.0)),
));
children.spawn((
Collider::sphere(0.5),
TransformBundle::from_transform(Transform::from_xyz(-2.0, 0.0, 0.0)),
));"
)]
#[cfg_attr(
feature = "3d",
doc = "- Creating colliders from meshes with [`AsyncCollider`] and [`AsyncSceneCollider`]"
)]
#[derive(Clone, Component)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
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 std::fmt::Debug for Collider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.shape_scaled().as_typed_shape() {
TypedShape::Ball(shape) => write!(f, "{:?}", shape),
TypedShape::Cuboid(shape) => write!(f, "{:?}", shape),
TypedShape::RoundCuboid(shape) => write!(f, "{:?}", shape),
TypedShape::Capsule(shape) => write!(f, "{:?}", shape),
TypedShape::Segment(shape) => write!(f, "{:?}", shape),
TypedShape::Triangle(shape) => write!(f, "{:?}", shape),
TypedShape::RoundTriangle(shape) => write!(f, "{:?}", shape),
TypedShape::TriMesh(_) => write!(f, "Trimesh (not representable)"),
TypedShape::Polyline(_) => write!(f, "Polyline (not representable)"),
TypedShape::HalfSpace(shape) => write!(f, "{:?}", shape),
TypedShape::HeightField(shape) => write!(f, "{:?}", shape),
TypedShape::Compound(_) => write!(f, "Compound (not representable)"),
TypedShape::Custom(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::ConvexPolyhedron(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::Cylinder(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::Cone(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::RoundCylinder(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::RoundCone(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "3d")]
TypedShape::RoundConvexPolyhedron(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "2d")]
TypedShape::ConvexPolygon(shape) => write!(f, "{:?}", shape),
#[cfg(feature = "2d")]
TypedShape::RoundConvexPolygon(shape) => write!(f, "{:?}", shape),
}
}
}
impl AnyCollider for Collider {
fn aabb(&self, position: Vector, rotation: impl Into<Rotation>) -> ColliderAabb {
let aabb = self
.shape_scaled()
.compute_aabb(&utils::make_isometry(position, rotation));
ColliderAabb {
min: aabb.mins.into(),
max: aabb.maxs.into(),
}
}
fn mass_properties(&self, density: Scalar) -> ColliderMassProperties {
let props = self.shape_scaled().mass_properties(density);
ColliderMassProperties {
mass: Mass(props.mass()),
inverse_mass: InverseMass(props.inv_mass),
#[cfg(feature = "2d")]
inertia: Inertia(props.principal_inertia()),
#[cfg(feature = "3d")]
inertia: Inertia(props.reconstruct_inertia_matrix().into()),
#[cfg(feature = "2d")]
inverse_inertia: InverseInertia(1.0 / props.principal_inertia()),
#[cfg(feature = "3d")]
inverse_inertia: InverseInertia(props.reconstruct_inverse_inertia_matrix().into()),
center_of_mass: CenterOfMass(props.local_com.into()),
}
}
fn contact_manifolds(
&self,
other: &Self,
position1: Vector,
rotation1: impl Into<Rotation>,
position2: Vector,
rotation2: impl Into<Rotation>,
prediction_distance: Scalar,
) -> Vec<ContactManifold> {
contact_query::contact_manifolds(
self,
position1,
rotation1,
other,
position2,
rotation2,
prediction_distance,
)
}
}
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_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(
&utils::make_isometry(translation, rotation),
&point.into(),
solid,
);
(projection.point.into(), 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(
&utils::make_isometry(translation, rotation),
&point.into(),
solid,
)
}
pub fn contains_point(
&self,
translation: impl Into<Position>,
rotation: impl Into<Rotation>,
point: Vector,
) -> bool {
self.shape_scaled()
.contains_point(&utils::make_isometry(translation, rotation), &point.into())
}
pub fn cast_ray(
&self,
translation: impl Into<Position>,
rotation: impl Into<Rotation>,
ray_origin: Vector,
ray_direction: Vector,
max_time_of_impact: Scalar,
solid: bool,
) -> Option<(Scalar, Vector)> {
let hit = self.shape_scaled().cast_ray_and_get_normal(
&utils::make_isometry(translation, rotation),
&parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
max_time_of_impact,
solid,
);
hit.map(|hit| (hit.time_of_impact, hit.normal.into()))
}
pub fn intersects_ray(
&self,
translation: impl Into<Position>,
rotation: impl Into<Rotation>,
ray_origin: Vector,
ray_direction: Vector,
max_time_of_impact: Scalar,
) -> bool {
self.shape_scaled().intersects_ray(
&utils::make_isometry(translation, rotation),
&parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
max_time_of_impact,
)
}
pub fn compound(
shapes: Vec<(
impl Into<Position>,
impl Into<Rotation>,
impl Into<Collider>,
)>,
) -> Self {
let shapes = shapes
.into_iter()
.map(|(p, r, c)| {
(
utils::make_isometry(*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_attr(
feature = "2d",
deprecated(since = "0.4.0", note = "please use `Collider::circle` instead")
)]
#[cfg_attr(
feature = "3d",
deprecated(since = "0.4.0", note = "please use `Collider::sphere` instead")
)]
pub fn ball(radius: Scalar) -> Self {
SharedShape::ball(radius).into()
}
#[cfg(feature = "2d")]
pub fn ellipse(half_width: Scalar, half_height: Scalar) -> Self {
SharedShape::new(EllipseWrapper(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 = "2d")]
#[deprecated(since = "0.4.0", note = "please use `Collider::rectangle` instead")]
pub fn cuboid(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 = "2d")]
#[deprecated(
since = "0.4.0",
note = "please use `Collider::round_rectangle` instead"
)]
pub fn round_cuboid(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(height: Scalar, radius: Scalar) -> Self {
SharedShape::cylinder(height * 0.5, radius).into()
}
#[cfg(feature = "3d")]
pub fn cone(height: Scalar, radius: Scalar) -> Self {
SharedShape::cone(height * 0.5, radius).into()
}
pub fn capsule(height: Scalar, radius: Scalar) -> Self {
SharedShape::capsule(
(Vector::Y * height * 0.5).into(),
(Vector::NEG_Y * height * 0.5).into(),
radius,
)
.into()
}
pub fn capsule_endpoints(a: Vector, b: Vector, radius: Scalar) -> Self {
SharedShape::capsule(a.into(), b.into(), radius).into()
}
pub fn halfspace(outward_normal: Vector) -> Self {
SharedShape::halfspace(nalgebra::Unit::new_normalize(outward_normal.into())).into()
}
pub fn segment(a: Vector, b: Vector) -> Self {
SharedShape::segment(a.into(), b.into()).into()
}
pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
SharedShape::triangle(a.into(), b.into(), c.into()).into()
}
#[cfg(feature = "2d")]
pub fn regular_polygon(circumradius: f32, sides: usize) -> Self {
RegularPolygon::new(circumradius, sides).collider()
}
pub fn polyline(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
let vertices = vertices.into_iter().map(|v| v.into()).collect();
SharedShape::polyline(vertices, indices).into()
}
pub fn trimesh(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
let vertices = vertices.into_iter().map(|v| v.into()).collect();
SharedShape::trimesh(vertices, indices).into()
}
pub fn trimesh_with_config(
vertices: Vec<Vector>,
indices: Vec<[u32; 3]>,
flags: TriMeshFlags,
) -> Self {
let vertices = vertices.into_iter().map(|v| v.into()).collect();
SharedShape::trimesh_with_flags(vertices, indices, flags).into()
}
#[cfg(feature = "2d")]
pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 2]>) -> Self {
let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
SharedShape::convex_decomposition(&vertices, &indices).into()
}
#[cfg(feature = "3d")]
pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
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 {
let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
SharedShape::convex_decomposition_with_params(&vertices, &indices, params).into()
}
#[cfg(feature = "3d")]
pub fn convex_decomposition_with_config(
vertices: Vec<Vector>,
indices: Vec<[u32; 3]>,
params: &VHACDParameters,
) -> Self {
let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
SharedShape::convex_decomposition_with_params(&vertices, &indices, params).into()
}
#[cfg(feature = "2d")]
pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
SharedShape::convex_hull(&points).map(Into::into)
}
#[cfg(feature = "3d")]
pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
SharedShape::convex_hull(&points).map(Into::into)
}
#[cfg(feature = "2d")]
pub fn heightfield(heights: Vec<Scalar>, scale: Vector) -> Self {
SharedShape::heightfield(heights.into(), scale.into()).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 = nalgebra::DMatrix::from_vec(row_count, column_count, data);
SharedShape::heightfield(heights, scale.into()).into()
}
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
SharedShape::trimesh_with_flags(
vertices,
indices,
TriMeshFlags::MERGE_DUPLICATE_VERTICES,
)
.into()
})
}
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TriMeshFlags) -> Option<Self> {
extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
SharedShape::trimesh_with_flags(vertices, indices, flags).into()
})
}
#[cfg(all(feature = "3d", 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(all(feature = "3d", 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(all(feature = "3d", 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).into()
})
}
}
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
type VerticesIndices = (Vec<nalgebra::Point3<Scalar>>, Vec<[u32; 3]>);
#[cfg(all(feature = "3d", 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> {
match shape.as_typed_shape() {
TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(&scale.into()))),
TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
border_radius: s.border_radius,
inner_shape: s.inner_shape.scaled(&scale.into()),
})),
TypedShape::Capsule(c) => match c.scaled(&scale.into(), 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))
} else {
Ok(SharedShape::new(EllipseWrapper(Ellipse {
half_size: Vec2::splat(b.radius as f32) * scale.f32(),
})))
}
}
#[cfg(feature = "3d")]
match b.scaled(&scale.into(), 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.into()))),
TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(&scale.into()))),
TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
border_radius: t.border_radius,
inner_shape: t.inner_shape.scaled(&scale.into()),
})),
TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(&scale.into()))),
TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(&scale.into()))),
TypedShape::HalfSpace(h) => match h.scaled(&scale.into()) {
None => {
log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
Ok(SharedShape::ball(0.0))
}
Some(scaled) => Ok(SharedShape::new(scaled)),
},
TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(&scale.into()))),
#[cfg(feature = "2d")]
TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(&scale.into()) {
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.into()) {
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.into()) {
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.into()) {
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.into(), 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.into(), 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.into(), 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.into(), 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 (iso, shape) in c.shapes() {
scaled.push((
#[cfg(feature = "2d")]
make_isometry(
Vector::from(iso.translation) * scale,
Rotation::from_radians(iso.rotation.angle()),
),
#[cfg(feature = "3d")]
make_isometry(
Vector::from(iso.translation) * scale,
Quaternion::from(iso.rotation),
),
scale_shape(shape, scale, num_subdivisions)?,
));
}
Ok(SharedShape::compound(scaled))
}
TypedShape::Custom(_id) => {
#[cfg(feature = "2d")]
if _id == 1 {
if let Some(ellipse) = shape.as_shape::<EllipseWrapper>() {
return Ok(SharedShape::new(EllipseWrapper(Ellipse {
half_size: ellipse.half_size * scale.f32(),
})));
}
} else if _id == 2 {
if let Some(polygon) = shape.as_shape::<RegularPolygonWrapper>() {
if scale.x == scale.y {
return Ok(SharedShape::new(RegularPolygonWrapper(
RegularPolygon::new(
polygon.circumradius() * scale.x as f32,
polygon.sides,
),
)));
} else {
let vertices = polygon
.vertices(0.0)
.into_iter()
.map(|v| v.adjust_precision().into())
.collect::<Vec<_>>();
return scale_shape(
&SharedShape::convex_hull(&vertices).unwrap(),
scale,
num_subdivisions,
);
}
}
}
Err(parry::query::Unsupported)
}
}
}