use crate::{
ancestor_marker::{AncestorMarker, AncestorMarkerPlugin},
physics_transform::PhysicsTransformSystems,
prelude::*,
};
use bevy::{
ecs::{intern::Interned, schedule::ScheduleLabel},
prelude::*,
};
pub struct ColliderTransformPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl ColliderTransformPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for ColliderTransformPlugin {
fn default() -> Self {
Self {
schedule: FixedPostUpdate.intern(),
}
}
}
impl Plugin for ColliderTransformPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(AncestorMarkerPlugin::<ColliderMarker>::default());
app.add_systems(
self.schedule,
propagate_collider_transforms.in_set(PhysicsTransformSystems::Propagate),
);
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule
.add_systems(update_child_collider_position.in_set(PhysicsStepSystems::First));
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn update_child_collider_position(
mut collider_query: Query<
(
&ColliderTransform,
&mut Position,
&mut Rotation,
&ColliderOf,
),
Without<RigidBody>,
>,
rb_query: Query<(&Position, &Rotation), (With<RigidBody>, With<Children>)>,
) {
for (collider_transform, mut position, mut rotation, collider_of) in &mut collider_query {
let Ok((rb_pos, rb_rot)) = rb_query.get(collider_of.body) else {
continue;
};
position.0 = rb_pos.0 + rb_rot * collider_transform.translation;
#[cfg(feature = "2d")]
{
*rotation = *rb_rot * collider_transform.rotation;
}
#[cfg(feature = "3d")]
{
*rotation = (rb_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<ChildOf>, With<AncestorMarker<ColliderMarker>>),
>,
collider_query: Query<
(
Ref<Transform>,
Option<&mut ColliderTransform>,
Option<&Children>,
),
(With<ChildOf>, ShouldPropagate),
>,
parent_query: Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<ChildOf>), ShouldPropagate>,
) {
root_query.par_iter_mut().for_each(
|(entity, transform, children)| {
for (child, child_transform, is_child_rb, child_of) in parent_query.iter_many(children) {
assert_eq!(
child_of.parent(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
let changed = transform.is_changed() || child_of.is_changed();
let parent_transform = ColliderTransform::from(*transform);
let child_transform = ColliderTransform::from(*child_transform);
let scale = parent_transform.scale * child_transform.scale;
unsafe {
propagate_collider_transforms_recursive(
if is_child_rb {
ColliderTransform {
scale,
..default()
}
} else {
ColliderTransform {
translation: parent_transform.scale * child_transform.translation,
rotation: child_transform.rotation,
scale,
}
},
&collider_query,
&parent_query,
child,
changed,
);
}
}
},
);
}
#[allow(clippy::type_complexity)]
unsafe fn propagate_collider_transforms_recursive(
transform: ColliderTransform,
collider_query: &Query<
(
Ref<Transform>,
Option<&mut ColliderTransform>,
Option<&Children>,
),
(With<ChildOf>, ShouldPropagate),
>,
parent_query: &Query<(Entity, Ref<Transform>, Has<RigidBody>, Ref<ChildOf>), 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() || collider_transform.as_ref().is_some_and(|t| t.is_added());
if changed
&& let Some(mut collider_transform) = collider_transform
&& *collider_transform != transform
{
*collider_transform = transform;
}
children
};
let Some(children) = children else { return };
for (child, child_transform, is_rb, child_of) in parent_query.iter_many(children) {
assert_eq!(
child_of.parent(),
entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
let child_transform = ColliderTransform::from(*child_transform);
let scale = transform.scale * child_transform.scale;
unsafe {
propagate_collider_transforms_recursive(
if is_rb {
ColliderTransform { 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,
}
},
collider_query,
parent_query,
child,
changed || child_of.is_changed(),
);
}
}
}