use crate::{prelude::*, utils::get_pos_translation};
use ancestor_marker::{AncestorMarker, AncestorMarkerPlugin};
use bevy::{ecs::intern::Interned, prelude::*};
use prepare::PrepareSet;
pub mod ancestor_marker;
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)
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct MarkRigidBodyAncestors;
impl Plugin for SyncPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SyncConfig>()
.register_type::<SyncConfig>();
app.configure_sets(
self.schedule,
(
SyncSet::First,
SyncSet::TransformToPosition,
SyncSet::PositionToTransform,
SyncSet::Update,
SyncSet::Last,
)
.chain()
.in_set(PhysicsSet::Sync),
);
app.add_plugins(
AncestorMarkerPlugin::<RigidBody>::new(self.schedule)
.add_markers_in_set(MarkRigidBodyAncestors),
);
app.configure_sets(
self.schedule,
MarkRigidBodyAncestors.in_set(PrepareSet::PreInit),
);
app.add_systems(
self.schedule,
(
sync_simple_transforms_physics,
propagate_transforms_physics,
init_previous_global_transform,
transform_to_position,
update_previous_global_transforms,
)
.chain()
.after(PhysicsSet::Prepare)
.before(PhysicsSet::StepSimulation)
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
);
app.add_systems(
self.schedule,
(
sync_simple_transforms_physics,
propagate_transforms_physics,
transform_to_position,
)
.chain()
.in_set(SyncSet::TransformToPosition)
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
);
app.add_systems(
self.schedule,
position_to_transform
.in_set(SyncSet::PositionToTransform)
.run_if(|config: Res<SyncConfig>| config.position_to_transform),
);
app.add_systems(
self.schedule,
(
sync_simple_transforms_physics,
propagate_transforms_physics,
update_previous_global_transforms,
)
.chain()
.in_set(SyncSet::Update)
.run_if(|config: Res<SyncConfig>| config.transform_to_position),
);
}
}
#[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(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SyncSet {
First,
TransformToPosition,
PositionToTransform,
Update,
Last,
}
#[derive(Component, Reflect, Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq)]
#[reflect(Component)]
pub struct PreviousGlobalTransform(pub GlobalTransform);
#[allow(clippy::type_complexity)]
pub(crate) fn init_previous_global_transform(
mut commands: Commands,
query: Query<(Entity, &GlobalTransform), Or<(Added<Position>, Added<Rotation>)>>,
) {
for (entity, transform) in &query {
commands
.entity(entity)
.try_insert(PreviousGlobalTransform(*transform));
}
}
#[allow(clippy::type_complexity)]
pub fn transform_to_position(
mut query: Query<(
&GlobalTransform,
&PreviousGlobalTransform,
&mut Position,
Option<&AccumulatedTranslation>,
&mut Rotation,
Option<&PreviousRotation>,
Option<&CenterOfMass>,
)>,
) {
for (
global_transform,
previous_transform,
mut position,
accumulated_translation,
mut rotation,
previous_rotation,
center_of_mass,
) 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| {
get_pos_translation(
t,
&previous_rotation.copied().unwrap_or_default(),
&rotation,
¢er_of_mass.copied().unwrap_or_default(),
)
});
#[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.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")]
pub 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.f32().extend(parent_transform.translation.z)
});
let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| {
Quaternion::from(*rot).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.f32()
.extend(parent_pos.z + transform.translation.z * parent_scale.z),
)
.with_rotation(Quaternion::from(*rot).f32()),
)
.reparented_to(&GlobalTransform::from(parent_transform));
transform.translation = new_transform.translation;
transform.rotation = new_transform.rotation;
}
} else {
transform.translation = pos.f32().extend(transform.translation.z);
transform.rotation = Quaternion::from(*rot).f32();
}
}
}
#[cfg(feature = "3d")]
pub 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.f32());
let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| rot.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.f32()).with_rotation(rot.f32()),
)
.reparented_to(&GlobalTransform::from(parent_transform));
transform.translation = new_transform.translation;
transform.rotation = new_transform.rotation;
}
} else {
transform.translation = pos.f32();
transform.rotation = rot.f32();
}
}
}
pub fn update_previous_global_transforms(
mut bodies: Query<(&GlobalTransform, &mut PreviousGlobalTransform)>,
) {
for (transform, mut previous_transform) in &mut bodies {
previous_transform.0 = *transform;
}
}
#[allow(clippy::type_complexity)]
pub fn sync_simple_transforms_physics(
mut query: ParamSet<(
Query<
(&Transform, &mut GlobalTransform),
(
Or<(Changed<Transform>, Added<GlobalTransform>)>,
Without<Parent>,
Or<(
Without<AncestorMarker<RigidBody>>,
Without<AncestorMarker<ColliderMarker>>,
)>,
Or<(With<RigidBody>, With<ColliderMarker>)>,
),
>,
Query<
(Ref<Transform>, &mut GlobalTransform),
(
Without<Parent>,
Or<(
Without<AncestorMarker<RigidBody>>,
Without<AncestorMarker<ColliderMarker>>,
)>,
Or<(With<RigidBody>, With<ColliderMarker>)>,
),
>,
)>,
mut orphaned: RemovedComponents<Parent>,
) {
query
.p0()
.par_iter_mut()
.for_each(|(transform, mut global_transform)| {
*global_transform = GlobalTransform::from(*transform);
});
let mut query = query.p1();
let mut iter = query.iter_many_mut(orphaned.read());
while let Some((transform, mut global_transform)) = iter.fetch_next() {
if !transform.is_changed() && !global_transform.is_added() {
*global_transform = GlobalTransform::from(*transform);
}
}
}
type TransformQueryData = (
Ref<'static, Transform>,
&'static mut GlobalTransform,
Option<&'static Children>,
Has<RigidBody>,
Has<ColliderMarker>,
);
type ParentQueryData = (
Entity,
Ref<'static, Parent>,
Has<RigidBody>,
Has<ColliderMarker>,
);
type PhysicsObjectOrAncestorFilter = Or<(
Or<(With<RigidBody>, With<AncestorMarker<RigidBody>>)>,
Or<(With<ColliderMarker>, With<AncestorMarker<ColliderMarker>>)>,
)>;
#[allow(clippy::type_complexity)]
pub fn propagate_transforms_physics(
mut root_query: Query<
(
Entity,
&Children,
Ref<Transform>,
&mut GlobalTransform,
Has<RigidBody>,
Has<ColliderMarker>,
),
(
Without<Parent>,
Or<(
With<AncestorMarker<RigidBody>>,
With<AncestorMarker<ColliderMarker>>,
)>,
),
>,
mut orphaned: RemovedComponents<Parent>,
transform_query: Query<TransformQueryData, With<Parent>>,
parent_query_1: Query<ParentQueryData>,
parent_query_2: Query<ParentQueryData, PhysicsObjectOrAncestorFilter>,
mut orphaned_entities: Local<Vec<Entity>>,
) {
orphaned_entities.clear();
orphaned_entities.extend(orphaned.read());
orphaned_entities.sort_unstable();
root_query.par_iter_mut().for_each(
|(entity, children, transform, mut global_transform, is_root_rb, is_root_collider)| {
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
if changed {
*global_transform = GlobalTransform::from(*transform);
}
let handle = |(child, actual_parent, is_parent_rb, is_parent_collider): (Entity, Ref<Parent>, bool, bool)| {
assert_eq!(
actual_parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
#[allow(unsafe_code)]
unsafe {
propagate_transforms_physics_recursive(
&global_transform,
&transform_query,
&parent_query_1,
&parent_query_2,
child,
changed || actual_parent.is_changed(),
is_parent_rb || is_parent_collider,
);
}
};
if is_root_rb || is_root_collider {
parent_query_1.iter_many(children).for_each(handle);
} else {
parent_query_2.iter_many(children).for_each(handle);
}
},
);
}
#[allow(clippy::type_complexity)]
unsafe fn propagate_transforms_physics_recursive(
parent: &GlobalTransform,
transform_query: &Query<TransformQueryData, With<Parent>>,
parent_query_1: &Query<ParentQueryData>,
parent_query_2: &Query<ParentQueryData, PhysicsObjectOrAncestorFilter>,
entity: Entity,
mut changed: bool,
mut any_ancestor_is_physics_entity: bool,
) {
let (global_matrix, children) = {
let Ok((transform, mut global_transform, children, is_rb, is_collider)) =
(unsafe { transform_query.get_unchecked(entity) }) else {
return;
};
if any_ancestor_is_physics_entity || is_rb || is_collider {
any_ancestor_is_physics_entity = true;
changed |= transform.is_changed() || global_transform.is_added();
if changed {
*global_transform = parent.mul_transform(*transform);
}
}
(*global_transform, children)
};
let Some(children) = children else { return };
if any_ancestor_is_physics_entity {
for (child, actual_parent, is_parent_rb, is_parent_collider) in
parent_query_1.iter_many(children)
{
assert_eq!(
actual_parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
unsafe {
propagate_transforms_physics_recursive(
&global_matrix,
transform_query,
parent_query_1,
parent_query_2,
child,
changed || actual_parent.is_changed(),
any_ancestor_is_physics_entity || is_parent_rb || is_parent_collider,
);
}
}
} else {
for (child, actual_parent, is_parent_rb, is_parent_collider) in
parent_query_2.iter_many(children)
{
assert_eq!(
actual_parent.get(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
unsafe {
propagate_transforms_physics_recursive(
&global_matrix,
transform_query,
parent_query_1,
parent_query_2,
child,
changed || actual_parent.is_changed(),
any_ancestor_is_physics_entity || is_parent_rb || is_parent_collider,
);
}
}
}
}