use crate::prelude::*;
use bevy::{ecs::query::Has, prelude::*, utils::intern::Interned};
pub struct SyncPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl SyncPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for SyncPlugin {
fn default() -> Self {
Self::new(PostUpdate)
}
}
impl Plugin for SyncPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SyncConfig>()
.register_type::<SyncConfig>();
app.add_systems(
self.schedule,
((
bevy::transform::systems::sync_simple_transforms,
bevy::transform::systems::propagate_transforms,
init_previous_global_transform,
transform_to_position,
update_previous_global_transforms,
)
.chain()
.after(PhysicsSet::Prepare)
.before(PhysicsSet::StepSimulation),)
.chain()
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
);
app.add_systems(
self.schedule,
(
(
bevy::transform::systems::sync_simple_transforms,
bevy::transform::systems::propagate_transforms,
transform_to_position,
)
.chain()
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
position_to_transform,
(
bevy::transform::systems::sync_simple_transforms,
bevy::transform::systems::propagate_transforms,
update_previous_global_transforms,
)
.chain()
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
update_collider_scale,
)
.chain()
.in_set(PhysicsSet::Sync)
.run_if(|config: Res<SyncConfig>| config.position_to_transform),
);
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(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
#[reflect(Resource)]
pub struct SyncConfig {
pub position_to_transform: bool,
pub transform_to_position: bool,
}
impl Default for SyncConfig {
fn default() -> Self {
SyncConfig {
position_to_transform: true,
transform_to_position: true,
}
}
}
#[derive(Component, Reflect, Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq)]
#[reflect(Component)]
pub struct PreviousGlobalTransform(pub GlobalTransform);
type PhysicsObjectAddedFilter = Or<(Added<RigidBody>, Added<Collider>)>;
fn init_previous_global_transform(
mut commands: Commands,
query: Query<(Entity, &GlobalTransform), PhysicsObjectAddedFilter>,
) {
for (entity, transform) in &query {
commands
.entity(entity)
.insert(PreviousGlobalTransform(*transform));
}
}
#[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(crate) fn update_collider_scale(
mut colliders: ParamSet<(
// Root bodies
Query<(&Transform, &mut Collider), Without<Parent>>,
// Child colliders
Query<(&ColliderTransform, &mut Collider), 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(),
);
}
}
}
fn transform_to_position(
mut query: Query<(
&GlobalTransform,
&PreviousGlobalTransform,
&mut Position,
Option<&AccumulatedTranslation>,
&mut Rotation,
)>,
) {
for (
global_transform,
previous_transform,
mut position,
accumulated_translation,
mut rotation,
) in &mut query
{
if *global_transform == previous_transform.0 {
continue;
}
let transform = global_transform.compute_transform();
let previous_transform = previous_transform.compute_transform();
let pos = position.0 + accumulated_translation.map_or(Vector::ZERO, |t| t.0);
#[cfg(feature = "2d")]
{
position.0 = (previous_transform.translation.truncate()
+ (transform.translation - previous_transform.translation).truncate())
.adjust_precision()
+ (pos - previous_transform.translation.truncate().adjust_precision());
}
#[cfg(feature = "3d")]
{
position.0 = (previous_transform.translation
+ (transform.translation - previous_transform.translation))
.adjust_precision()
+ (pos - previous_transform.translation.adjust_precision());
}
#[cfg(feature = "2d")]
{
let rot = Rotation::from(transform.rotation.adjust_precision());
let prev_rot = Rotation::from(previous_transform.rotation.adjust_precision());
*rotation = prev_rot + (rot - prev_rot) + (*rotation - prev_rot);
}
#[cfg(feature = "3d")]
{
rotation.0 = (previous_transform.rotation
+ (transform.rotation - previous_transform.rotation)
+ (rotation.as_f32() - previous_transform.rotation))
.normalize()
.adjust_precision();
}
}
}
type PosToTransformComponents = (
&'static mut Transform,
&'static Position,
&'static Rotation,
Option<&'static Parent>,
);
type PosToTransformFilter = (With<RigidBody>, Or<(Changed<Position>, Changed<Rotation>)>);
type ParentComponents = (
&'static GlobalTransform,
Option<&'static Position>,
Option<&'static Rotation>,
);
#[cfg(feature = "2d")]
fn position_to_transform(
mut query: Query<PosToTransformComponents, PosToTransformFilter>,
parents: Query<ParentComponents, With<Children>>,
) {
for (mut transform, pos, rot, parent) in &mut query {
if let Some(parent) = parent {
if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(**parent) {
let parent_transform = parent_transform.compute_transform();
let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| {
pos.as_f32().extend(parent_transform.translation.z)
});
let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| {
Quaternion::from(*rot).as_f32()
});
let parent_scale = parent_transform.scale;
let parent_transform = Transform::from_translation(parent_pos)
.with_rotation(parent_rot)
.with_scale(parent_scale);
let new_transform = GlobalTransform::from(
Transform::from_translation(
pos.as_f32()
.extend(parent_pos.z + transform.translation.z * parent_scale.z),
)
.with_rotation(Quaternion::from(*rot).as_f32()),
)
.reparented_to(&GlobalTransform::from(parent_transform));
transform.translation = new_transform.translation;
transform.rotation = new_transform.rotation;
}
} else {
transform.translation = pos.as_f32().extend(transform.translation.z);
transform.rotation = Quaternion::from(*rot).as_f32();
}
}
}
#[cfg(feature = "3d")]
fn position_to_transform(
mut query: Query<PosToTransformComponents, PosToTransformFilter>,
parents: Query<ParentComponents, With<Children>>,
) {
for (mut transform, pos, rot, parent) in &mut query {
if let Some(parent) = parent {
if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(**parent) {
let parent_transform = parent_transform.compute_transform();
let parent_pos =
parent_pos.map_or(parent_transform.translation, |pos| pos.as_f32());
let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| rot.as_f32());
let parent_scale = parent_transform.scale;
let parent_transform = Transform::from_translation(parent_pos)
.with_rotation(parent_rot)
.with_scale(parent_scale);
let new_transform = GlobalTransform::from(
Transform::from_translation(pos.as_f32()).with_rotation(rot.as_f32()),
)
.reparented_to(&GlobalTransform::from(parent_transform));
transform.translation = new_transform.translation;
transform.rotation = new_transform.rotation;
}
} else {
transform.translation = pos.as_f32();
transform.rotation = rot.as_f32();
}
}
}
fn update_previous_global_transforms(
mut bodies: Query<(&GlobalTransform, &mut PreviousGlobalTransform)>,
) {
for (transform, mut previous_transform) in &mut bodies {
previous_transform.0 = *transform;
}
}