use std::marker::PhantomData;
use crate::{
broad_phase::BroadPhaseSet,
prelude::*,
prepare::{any_new, PrepareSet},
sync::SyncSet,
};
#[cfg(all(
feature = "3d",
feature = "async-collider",
feature = "default-collider"
))]
use bevy::scene::SceneInstance;
use bevy::{
prelude::*,
utils::{intern::Interned, HashMap},
};
#[cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
pub struct ColliderBackendPlugin<C: ScalableCollider> {
schedule: Interned<dyn ScheduleLabel>,
_phantom: PhantomData<C>,
}
impl<C: ScalableCollider> ColliderBackendPlugin<C> {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
_phantom: PhantomData,
}
}
}
impl<C: ScalableCollider> Default for ColliderBackendPlugin<C> {
fn default() -> Self {
Self {
schedule: PostUpdate.intern(),
_phantom: PhantomData,
}
}
}
impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
fn build(&self, app: &mut App) {
app.init_resource::<ColliderStorageMap<C>>();
app.add_systems(
self.schedule,
(
(
init_colliders::<C>,
apply_deferred,
update_collider_parents::<C>,
apply_deferred,
)
.chain()
.in_set(PrepareSet::InitColliders),
init_transforms::<C>
.in_set(PrepareSet::InitTransforms)
.after(init_transforms::<RigidBody>),
(
(
propagate_collider_transforms,
update_child_collider_position,
)
.chain()
.run_if(any_new::<C>),
update_collider_mass_properties::<C>,
)
.chain()
.in_set(PrepareSet::Finalize)
.before(prepare::update_mass_properties),
update_collider_scale::<C>
.after(SyncSet::Update)
.before(SyncSet::Last),
),
);
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule.add_systems(
update_aabb::<C>
.in_set(PhysicsStepSet::BroadPhase)
.after(BroadPhaseSet::First)
.before(BroadPhaseSet::UpdateStructures)
.ambiguous_with_all(),
);
physics_schedule.add_systems((
update_collider_storage::<C>.before(PhysicsStepSet::BroadPhase),
handle_collider_storage_removals::<C>.after(PhysicsStepSet::SpatialQuery),
handle_rigid_body_removals.after(PhysicsStepSet::SpatialQuery),
));
physics_schedule.add_systems(
wake_on_collider_removed::<C>
.in_set(PhysicsStepSet::Sleeping)
.after(sleeping::mark_sleeping_bodies)
.before(sleeping::wake_on_changed)
.ambiguous_with_all(),
);
#[cfg(all(
feature = "3d",
feature = "async-collider",
feature = "default-collider"
))]
app.add_systems(Update, (init_async_colliders, init_async_scene_colliders));
let substep_schedule = app
.get_schedule_mut(SubstepSchedule)
.expect("add SubstepSchedule first");
substep_schedule.add_systems(
(
propagate_collider_transforms,
update_child_collider_position,
)
.chain()
.after(SubstepSet::Integrate)
.before(SubstepSet::NarrowPhase)
.ambiguous_with_all(),
);
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq)]
#[reflect(Component)]
pub(crate) struct PreviousColliderTransform(ColliderTransform);
#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
#[reflect(Resource)]
pub(crate) struct ColliderStorageMap<C: AnyCollider> {
pub(crate) map: HashMap<Entity, (ColliderParent, ColliderMassProperties, ColliderTransform)>,
_phantom: PhantomData<C>,
}
impl<C: AnyCollider> Default for ColliderStorageMap<C> {
fn default() -> Self {
Self {
map: default(),
_phantom: PhantomData,
}
}
}
#[allow(clippy::type_complexity)]
fn init_colliders<C: AnyCollider>(
mut commands: Commands,
mut colliders: Query<
(
Entity,
&C,
Option<&ColliderAabb>,
Option<&ColliderDensity>,
Option<&ColliderMassProperties>,
),
Added<C>,
>,
) {
for (entity, collider, aabb, density, mass_properties) in &mut colliders {
let density = *density.unwrap_or(&ColliderDensity::default());
commands.entity(entity).try_insert((
*aabb.unwrap_or(&collider.aabb(Vector::ZERO, Rotation::default())),
density,
*mass_properties.unwrap_or(&collider.mass_properties(density.0)),
CollidingEntities::default(),
));
}
}
#[cfg(all(
feature = "3d",
feature = "async-collider",
feature = "default-collider"
))]
pub fn init_async_colliders(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
async_colliders: Query<(Entity, &Handle<Mesh>, &AsyncCollider)>,
) {
for (entity, mesh_handle, async_collider) in async_colliders.iter() {
if let Some(mesh) = meshes.get(mesh_handle) {
let collider = match &async_collider.0 {
ComputedCollider::TriMesh => Collider::trimesh_from_mesh(mesh),
ComputedCollider::TriMeshWithFlags(flags) => {
Collider::trimesh_from_mesh_with_config(mesh, *flags)
}
ComputedCollider::ConvexHull => Collider::convex_hull_from_mesh(mesh),
ComputedCollider::ConvexDecomposition(params) => {
Collider::convex_decomposition_from_mesh_with_config(mesh, params)
}
};
if let Some(collider) = collider {
commands
.entity(entity)
.insert(collider)
.remove::<AsyncCollider>();
} else {
error!("Unable to generate collider from mesh {:?}", mesh);
}
}
}
}
#[cfg(all(
feature = "3d",
feature = "async-collider",
feature = "default-collider"
))]
pub fn init_async_scene_colliders(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
scene_spawner: Res<SceneSpawner>,
async_colliders: Query<(Entity, &SceneInstance, &AsyncSceneCollider)>,
children: Query<&Children>,
mesh_handles: Query<(&Name, &Handle<Mesh>)>,
) {
for (scene_entity, scene_instance, async_scene_collider) in async_colliders.iter() {
if scene_spawner.instance_is_ready(**scene_instance) {
for child_entity in children.iter_descendants(scene_entity) {
if let Ok((name, handle)) = mesh_handles.get(child_entity) {
let Some(collider_data) = async_scene_collider
.meshes_by_name
.get(name.as_str())
.cloned()
.unwrap_or(
async_scene_collider
.default_shape
.clone()
.map(|shape| AsyncSceneColliderData { shape, ..default() }),
)
else {
continue;
};
let mesh = meshes.get(handle).expect("mesh should already be loaded");
let collider = match collider_data.shape {
ComputedCollider::TriMesh => Collider::trimesh_from_mesh(mesh),
ComputedCollider::TriMeshWithFlags(flags) => {
Collider::trimesh_from_mesh_with_config(mesh, flags)
}
ComputedCollider::ConvexHull => Collider::convex_hull_from_mesh(mesh),
ComputedCollider::ConvexDecomposition(params) => {
Collider::convex_decomposition_from_mesh_with_config(mesh, ¶ms)
}
};
if let Some(collider) = collider {
commands.entity(child_entity).insert((
collider,
collider_data.layers,
ColliderDensity(collider_data.density),
));
} else {
error!(
"unable to generate collider from mesh {:?} with name {}",
mesh, name
);
}
}
}
commands.entity(scene_entity).remove::<AsyncSceneCollider>();
}
}
}
#[allow(clippy::type_complexity)]
fn update_aabb<C: AnyCollider>(
mut colliders: Query<
(
&C,
&mut ColliderAabb,
&Position,
&Rotation,
Option<&ColliderParent>,
Option<&LinearVelocity>,
Option<&AngularVelocity>,
),
Or<(
Changed<Position>,
Changed<Rotation>,
Changed<LinearVelocity>,
Changed<AngularVelocity>,
Changed<C>,
)>,
>,
parent_velocity: Query<
(&Position, Option<&LinearVelocity>, Option<&AngularVelocity>),
With<Children>,
>,
dt: Res<Time>,
narrow_phase_config: Option<Res<NarrowPhaseConfig>>,
) {
let safety_margin_factor = 2.0 * dt.delta_seconds_adjusted();
for (collider, mut aabb, pos, rot, collider_parent, lin_vel, ang_vel) in &mut colliders {
let (lin_vel, ang_vel) = if let (Some(lin_vel), Some(ang_vel)) = (lin_vel, ang_vel) {
(*lin_vel, *ang_vel)
} else if let Some(Ok((parent_pos, Some(lin_vel), Some(ang_vel)))) =
collider_parent.map(|p| parent_velocity.get(p.get()))
{
let offset = pos.0 - parent_pos.0;
#[cfg(feature = "2d")]
let vel_at_offset =
lin_vel.0 + Vector::new(-ang_vel.0 * offset.y, ang_vel.0 * offset.x) * 1.0;
#[cfg(feature = "3d")]
let vel_at_offset = lin_vel.0 + ang_vel.cross(offset);
(LinearVelocity(vel_at_offset), *ang_vel)
} else {
(LinearVelocity::ZERO, AngularVelocity::ZERO)
};
let (start_pos, start_rot) = (*pos, *rot);
let (end_pos, end_rot) = {
#[cfg(feature = "2d")]
{
(
pos.0 + lin_vel.0 * safety_margin_factor,
*rot + Rotation::from_radians(safety_margin_factor * ang_vel.0),
)
}
#[cfg(feature = "3d")]
{
let q = Quaternion::from_vec4(ang_vel.0.extend(0.0)) * rot.0;
let (x, y, z, w) = (
rot.x + safety_margin_factor * 0.5 * q.x,
rot.y + safety_margin_factor * 0.5 * q.y,
rot.z + safety_margin_factor * 0.5 * q.z,
rot.w + safety_margin_factor * 0.5 * q.w,
);
(
pos.0 + lin_vel.0 * safety_margin_factor,
Quaternion::from_xyzw(x, y, z, w).normalize(),
)
}
};
*aabb = collider.swept_aabb(start_pos.0, start_rot, end_pos, end_rot);
let prediction_distance = if let Some(ref config) = narrow_phase_config {
config.prediction_distance
} else {
#[cfg(feature = "2d")]
{
1.0
}
#[cfg(feature = "3d")]
{
0.005
}
};
aabb.max.x += prediction_distance;
aabb.min.x -= prediction_distance;
aabb.max.y += prediction_distance;
aabb.min.y -= prediction_distance;
#[cfg(feature = "3d")]
{
aabb.max.z += prediction_distance;
aabb.min.z -= prediction_distance;
}
}
}
#[allow(clippy::type_complexity)]
fn update_collider_parents<C: AnyCollider>(
mut commands: Commands,
mut bodies: Query<(Entity, Option<&mut ColliderParent>, Has<C>), With<RigidBody>>,
children: Query<&Children>,
mut child_colliders: Query<Option<&mut ColliderParent>, (With<C>, Without<RigidBody>)>,
) {
for (entity, collider_parent, has_collider) in &mut bodies {
if has_collider {
if let Some(mut collider_parent) = collider_parent {
collider_parent.0 = entity;
} else {
commands.entity(entity).try_insert((
ColliderParent(entity),
ColliderTransform::default(),
PreviousColliderTransform::default(),
));
}
}
for child in children.iter_descendants(entity) {
if let Ok(collider_parent) = child_colliders.get_mut(child) {
if let Some(mut collider_parent) = collider_parent {
collider_parent.0 = entity;
} else {
commands.entity(child).insert((
ColliderParent(entity),
ColliderTransform::default(),
PreviousColliderTransform::default(),
));
}
}
}
}
}
fn handle_rigid_body_removals(
mut commands: Commands,
colliders: Query<(Entity, &ColliderParent), Without<RigidBody>>,
bodies: Query<(), With<RigidBody>>,
removals: RemovedComponents<RigidBody>,
) {
if removals.is_empty() {
return;
}
for (collider_entity, collider_parent) in &colliders {
if !bodies.contains(collider_parent.get()) {
commands.entity(collider_entity).remove::<(
ColliderParent,
ColliderTransform,
PreviousColliderTransform,
)>();
}
}
}
#[allow(clippy::type_complexity)]
fn update_collider_storage<C: AnyCollider>(
colliders: Query<
(
Entity,
&ColliderParent,
&ColliderMassProperties,
&ColliderTransform,
),
Or<(
Changed<ColliderParent>,
Changed<ColliderTransform>,
Changed<ColliderMassProperties>,
)>,
>,
mut storage: ResMut<ColliderStorageMap<C>>,
) {
for (entity, parent, collider_mass_properties, collider_transform) in &colliders {
storage.map.insert(
entity,
(*parent, *collider_mass_properties, *collider_transform),
);
}
}
fn handle_collider_storage_removals<C: AnyCollider>(
mut removals: RemovedComponents<C>,
mut storage: ResMut<ColliderStorageMap<C>>,
) {
removals.read().for_each(|entity| {
storage.map.remove(&entity);
});
}
#[allow(clippy::type_complexity)]
pub(crate) fn update_child_collider_position(
mut colliders: Query<
(
&ColliderTransform,
&mut Position,
&mut Rotation,
&ColliderParent,
),
Without<RigidBody>,
>,
parents: Query<(&Position, &Rotation), (With<RigidBody>, With<Children>)>,
) {
for (collider_transform, mut position, mut rotation, parent) in &mut colliders {
let Ok((parent_pos, parent_rot)) = parents.get(parent.get()) else {
continue;
};
position.0 = parent_pos.0 + parent_rot.rotate(collider_transform.translation);
#[cfg(feature = "2d")]
{
*rotation = *parent_rot + collider_transform.rotation;
}
#[cfg(feature = "3d")]
{
*rotation = (parent_rot.0 * collider_transform.rotation.0)
.normalize()
.into();
}
}
}
#[allow(clippy::type_complexity)]
pub fn update_collider_scale<C: ScalableCollider>(
mut colliders: ParamSet<(
// Root bodies
Query<(&Transform, &mut C), Without<Parent>>,
// Child colliders
Query<(&ColliderTransform, &mut C), With<Parent>>,
)>,
) {
for (transform, mut collider) in &mut colliders.p0() {
#[cfg(feature = "2d")]
let scale = transform.scale.truncate().adjust_precision();
#[cfg(feature = "3d")]
let scale = transform.scale.adjust_precision();
if scale != collider.scale() {
collider.set_scale(scale, 10);
}
}
for (collider_transform, mut collider) in &mut colliders.p1() {
if collider_transform.scale != collider.scale() {
collider.set_scale(collider_transform.scale, 10);
}
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn propagate_collider_transforms(
mut root_query: Query<(Entity, Ref<Transform>, &Children), Without<Parent>>,
collider_query: Query<
(
Ref<Transform>,
Option<&mut ColliderTransform>,
Option<&Children>,
),
With<Parent>,
>,
parent_query: Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<Parent>)>,
) {
root_query.par_iter_mut().for_each(
|(entity, transform,children)| {
for (child, child_transform, is_child_rb, parent) in parent_query.iter_many(children) {
assert_eq!(
parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
let child_transform = ColliderTransform::from(*child_transform);
unsafe {
propagate_collider_transforms_recursive(
if is_child_rb {
ColliderTransform {
scale: child_transform.scale,
..default()
}
} else {
let transform = ColliderTransform::from(*transform);
ColliderTransform {
translation: transform.scale * child_transform.translation,
rotation: child_transform.rotation,
scale: (transform.scale * child_transform.scale).max(Vector::splat(Scalar::EPSILON)),
}
},
&collider_query,
&parent_query,
child,
transform.is_changed() || parent.is_changed()
);
}
}
},
);
}
#[allow(clippy::type_complexity)]
unsafe fn propagate_collider_transforms_recursive(
transform: ColliderTransform,
collider_query: &Query<
(
Ref<Transform>,
Option<&mut ColliderTransform>,
Option<&Children>,
),
With<Parent>,
>,
parent_query: &Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<Parent>)>,
entity: Entity,
mut changed: bool,
) {
let children = {
let Ok((transform_ref, collider_transform, children)) =
(unsafe { collider_query.get_unchecked(entity) })
else {
return;
};
changed |= transform_ref.is_changed();
if changed {
if let Some(mut collider_transform) = collider_transform {
if *collider_transform != transform {
*collider_transform = transform;
}
}
}
children
};
let Some(children) = children else { return };
for (child, child_transform, is_rb, parent) in parent_query.iter_many(children) {
assert_eq!(
parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
let child_transform = ColliderTransform::from(*child_transform);
unsafe {
propagate_collider_transforms_recursive(
if is_rb {
ColliderTransform {
scale: child_transform.scale,
..default()
}
} else {
ColliderTransform {
translation: transform.transform_point(child_transform.translation),
#[cfg(feature = "2d")]
rotation: transform.rotation + child_transform.rotation,
#[cfg(feature = "3d")]
rotation: Rotation(transform.rotation.0 * child_transform.rotation.0),
scale: (transform.scale * child_transform.scale)
.max(Vector::splat(Scalar::EPSILON)),
}
},
collider_query,
parent_query,
child,
changed || parent.is_changed(),
);
}
}
}
#[allow(clippy::type_complexity)]
fn update_collider_mass_properties<C: AnyCollider>(
mut mass_props: Query<(Entity, MassPropertiesQuery)>,
mut colliders: Query<
(
&ColliderTransform,
&mut PreviousColliderTransform,
&ColliderParent,
Ref<C>,
&ColliderDensity,
&mut ColliderMassProperties,
),
Or<(
Changed<C>,
Changed<ColliderTransform>,
Changed<ColliderDensity>,
Changed<ColliderMassProperties>,
)>,
>,
collider_map: Res<ColliderStorageMap<C>>,
mut removed_colliders: RemovedComponents<C>,
) {
for (
collider_transform,
mut previous_collider_transform,
collider_parent,
collider,
density,
mut collider_mass_properties,
) in &mut colliders
{
if let Ok((_, mut mass_properties)) = mass_props.get_mut(collider_parent.0) {
if !collider.is_added() {
mass_properties -= ColliderMassProperties {
center_of_mass: CenterOfMass(
previous_collider_transform
.transform_point(collider_mass_properties.center_of_mass.0),
),
..*collider_mass_properties
};
}
previous_collider_transform.0 = *collider_transform;
*collider_mass_properties = collider.mass_properties(density.max(Scalar::EPSILON));
mass_properties += ColliderMassProperties {
center_of_mass: CenterOfMass(
collider_transform.transform_point(collider_mass_properties.center_of_mass.0),
),
..*collider_mass_properties
};
}
}
for entity in removed_colliders.read() {
if let Some((collider_parent, collider_mass_properties, collider_transform)) =
collider_map.map.get(&entity)
{
if let Ok((_, mut mass_properties)) = mass_props.get_mut(collider_parent.0) {
mass_properties -= ColliderMassProperties {
center_of_mass: CenterOfMass(
collider_transform
.transform_point(collider_mass_properties.center_of_mass.0),
),
..*collider_mass_properties
};
}
}
}
}
#[allow(clippy::type_complexity)]
fn wake_on_collider_removed<C: AnyCollider>(
mut commands: Commands,
mut bodies: Query<(Entity, &mut TimeSleeping), With<RigidBody>>,
all_colliders: Query<&ColliderParent>,
child_colliders: Query<
&ColliderParent,
(
Without<RigidBody>,
Or<(Changed<C>, Changed<Transform>, Changed<ColliderTransform>)>,
),
>,
mut removed_colliders: RemovedComponents<C>,
collider_storage: Res<ColliderStorageMap<C>>,
) {
let removed_colliders_iter =
all_colliders.iter_many(removed_colliders.read().filter_map(|entity| {
collider_storage
.map
.get(&entity)
.map(|(rb_entity, _, _)| rb_entity.get())
}));
for collider_parent in child_colliders.iter().chain(removed_colliders_iter) {
if let Ok((entity, mut time_sleeping)) = bodies.get_mut(collider_parent.get()) {
commands.entity(entity).remove::<Sleeping>();
time_sleeping.0 = 0.0;
}
}
}