use std::fmt;
use crate::{prelude::*, utils::make_isometry};
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
use bevy::render::mesh::{Indices, VertexAttributeValues};
#[cfg(all(feature = "3d", feature = "async-collider"))]
use bevy::utils::HashMap;
use bevy::{log, prelude::*, utils::HashSet};
use collision::contact_query::UnsupportedShape;
use itertools::Either;
use parry::{
bounding_volume::Aabb,
shape::{RoundShape, SharedShape, TypedShape},
};
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 = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = " commands.spawn((RigidBody::Static, Collider::cuboid(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 = "3d",
doc = "- Creating colliders from meshes with [`AsyncCollider`] and [`AsyncSceneCollider`]"
)]
#[derive(Clone, Component)]
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::cuboid(0.5, 0.5)
}
#[cfg(feature = "3d")]
{
Self::cuboid(0.5, 0.5, 0.5)
}
}
}
impl fmt::Debug for Collider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 Collider {
pub fn shape(&self) -> &SharedShape {
&self.shape
}
pub fn shape_scaled(&self) -> &SharedShape {
&self.scaled_shape
}
pub fn scale(&self) -> Vector {
self.scale
}
pub fn set_shape(&mut self, shape: SharedShape) {
if self.scale != Vector::ONE {
self.scaled_shape = shape.clone();
}
self.shape = shape;
}
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.");
}
}
#[cfg(feature = "2d")]
pub fn compute_aabb(&self, position: Vector, rotation: Scalar) -> ColliderAabb {
ColliderAabb(self.shape_scaled().compute_aabb(&utils::make_isometry(
position,
Rotation::from_radians(rotation),
)))
}
#[cfg(feature = "3d")]
pub fn compute_aabb(&self, position: Vector, rotation: Quaternion) -> ColliderAabb {
ColliderAabb(
self.shape_scaled()
.compute_aabb(&utils::make_isometry(position, Rotation(rotation))),
)
}
pub fn mass_properties(&self, density: Scalar) -> ColliderMassProperties {
ColliderMassProperties::new(self, density)
}
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()
}
pub fn ball(radius: Scalar) -> Self {
SharedShape::ball(radius).into()
}
#[cfg(feature = "2d")]
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 = "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()
}
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: Scalar) -> Self {
SharedShape::heightfield(heights.into(), Vector::splat(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) => 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))
}
_ => Err(parry::query::Unsupported),
}
}
#[cfg(all(feature = "3d", feature = "async-collider"))]
#[derive(Component, Clone, Debug, Default, Deref, DerefMut)]
pub struct AsyncCollider(pub ComputedCollider);
#[cfg(all(feature = "3d", feature = "async-collider"))]
#[derive(Component, Clone, Debug, Default, PartialEq)]
pub struct AsyncSceneCollider {
pub default_shape: Option<ComputedCollider>,
pub meshes_by_name: HashMap<String, Option<AsyncSceneColliderData>>,
}
#[cfg(all(feature = "3d", feature = "async-collider"))]
impl AsyncSceneCollider {
pub fn new(default_shape: Option<ComputedCollider>) -> Self {
Self {
default_shape,
meshes_by_name: default(),
}
}
pub fn with_shape_for_name(mut self, name: &str, shape: ComputedCollider) -> Self {
if let Some(Some(data)) = self.meshes_by_name.get_mut(name) {
data.shape = shape;
} else {
self.meshes_by_name.insert(
name.to_string(),
Some(AsyncSceneColliderData { shape, ..default() }),
);
}
self
}
pub fn with_layers_for_name(mut self, name: &str, layers: CollisionLayers) -> Self {
if let Some(Some(data)) = self.meshes_by_name.get_mut(name) {
data.layers = layers;
} else {
self.meshes_by_name.insert(
name.to_string(),
Some(AsyncSceneColliderData {
layers,
..default()
}),
);
}
self
}
pub fn with_density_for_name(mut self, name: &str, density: Scalar) -> Self {
if let Some(Some(data)) = self.meshes_by_name.get_mut(name) {
data.density = density;
} else {
self.meshes_by_name.insert(
name.to_string(),
Some(AsyncSceneColliderData {
density,
..default()
}),
);
}
self
}
pub fn without_shape_with_name(mut self, name: &str) -> Self {
self.meshes_by_name.insert(name.to_string(), None);
self
}
}
#[cfg(all(feature = "3d", feature = "async-collider"))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncSceneColliderData {
pub shape: ComputedCollider,
pub layers: CollisionLayers,
pub density: Scalar,
}
#[cfg(all(feature = "3d", feature = "async-collider"))]
impl Default for AsyncSceneColliderData {
fn default() -> Self {
Self {
shape: ComputedCollider::TriMesh,
layers: CollisionLayers::default(),
density: 1.0,
}
}
}
#[cfg(all(feature = "3d", feature = "collider-from-mesh"))]
#[derive(Component, Clone, Debug, Default, PartialEq)]
pub enum ComputedCollider {
#[default]
TriMesh,
ConvexHull,
ConvexDecomposition(VHACDParameters),
}
#[cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, Eq)]
pub struct ColliderParent(pub(crate) Entity);
impl ColliderParent {
pub const fn get(&self) -> Entity {
self.0
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq)]
pub struct ColliderTransform {
pub translation: Vector,
pub rotation: Rotation,
pub scale: Vector,
}
impl ColliderTransform {
pub fn transform_point(&self, mut point: Vector) -> Vector {
point *= self.scale;
point = self.rotation.rotate(point);
point += self.translation;
point
}
}
impl Default for ColliderTransform {
fn default() -> Self {
Self {
translation: Vector::ZERO,
rotation: Rotation::default(),
scale: Vector::ONE,
}
}
}
impl From<Transform> for ColliderTransform {
fn from(value: Transform) -> Self {
Self {
#[cfg(feature = "2d")]
translation: value.translation.truncate().adjust_precision(),
#[cfg(feature = "3d")]
translation: value.translation.adjust_precision(),
rotation: Rotation::from(value.rotation.adjust_precision()),
#[cfg(feature = "2d")]
scale: value.scale.truncate().adjust_precision(),
#[cfg(feature = "3d")]
scale: value.scale.adjust_precision(),
}
}
}
#[cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
#[doc(alias = "Trigger")]
#[derive(Reflect, Clone, Component, Debug, Default, PartialEq, Eq)]
#[reflect(Component)]
pub struct Sensor;
#[derive(Clone, Copy, Component, Debug, Deref, DerefMut, PartialEq)]
pub struct ColliderAabb(pub Aabb);
impl ColliderAabb {
pub fn from_shape(shape: &SharedShape) -> Self {
Self(shape.compute_local_aabb())
}
}
impl Default for ColliderAabb {
fn default() -> Self {
ColliderAabb(Aabb::new_invalid())
}
}
#[cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
#[derive(Reflect, Clone, Component, Debug, Default, Deref, DerefMut, PartialEq, Eq)]
#[reflect(Component)]
pub struct CollidingEntities(pub HashSet<Entity>);