use crate::{
prelude::*,
prepare::{match_any, PrepareSet},
};
use bevy::{ecs::intern::Interned, prelude::*};
use sync::ancestor_marker::{AncestorMarker, AncestorMarkerPlugin};
pub struct ColliderHierarchyPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl ColliderHierarchyPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for ColliderHierarchyPlugin {
fn default() -> Self {
Self {
schedule: PostUpdate.intern(),
}
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct MarkColliderAncestors;
impl Plugin for ColliderHierarchyPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(
AncestorMarkerPlugin::<ColliderMarker>::new(self.schedule)
.add_markers_in_set(MarkColliderAncestors),
);
app.configure_sets(
self.schedule,
MarkColliderAncestors
.after(PrepareSet::InitColliders)
.before(PrepareSet::PropagateTransforms),
);
app.add_systems(
self.schedule,
update_collider_parents
.after(PrepareSet::InitColliders)
.before(PrepareSet::Finalize),
);
app.add_systems(
self.schedule,
(
sync::sync_simple_transforms_physics,
sync::propagate_transforms_physics,
)
.chain()
.run_if(match_any::<(Added<ColliderMarker>, Without<RigidBody>)>)
.in_set(PrepareSet::PropagateTransforms)
.ambiguous_with_all(),
);
app.add_systems(
self.schedule,
(
propagate_collider_transforms,
update_child_collider_position,
)
.chain()
.run_if(match_any::<Added<ColliderMarker>>)
.after(PrepareSet::InitTransforms)
.before(PrepareSet::Finalize),
);
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule
.add_systems(handle_rigid_body_removals.after(PhysicsStepSet::SpatialQuery));
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),
);
}
}
#[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, DerefMut, PartialEq)]
#[reflect(Component)]
pub(crate) struct PreviousColliderTransform(pub ColliderTransform);
#[allow(clippy::type_complexity)]
fn update_collider_parents(
mut commands: Commands,
mut bodies: Query<(Entity, Option<&mut ColliderParent>, Has<ColliderMarker>), With<RigidBody>>,
children: Query<&Children>,
mut child_colliders: Query<
Option<&mut ColliderParent>,
(With<ColliderMarker>, 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)]
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();
}
}
}
type ShouldPropagate = Or<(With<AncestorMarker<ColliderMarker>>, With<ColliderMarker>)>;
#[allow(clippy::type_complexity)]
pub(crate) fn propagate_collider_transforms(
mut root_query: Query<
(Entity, Ref<Transform>, &Children),
(Without<Parent>, With<AncestorMarker<ColliderMarker>>),
>,
collider_query: Query<
(
Ref<Transform>,
Option<&mut ColliderTransform>,
Option<&Children>,
),
(With<Parent>, ShouldPropagate),
>,
parent_query: Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<Parent>), ShouldPropagate>,
) {
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>, ShouldPropagate),
>,
parent_query: &Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<Parent>), ShouldPropagate>,
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(),
);
}
}
}