mod transform;
pub use transform::{Position, PreSolveDeltaPosition, PreSolveDeltaRotation, Rotation};
#[allow(unused_imports)]
pub(crate) use transform::{RotationValue, init_physics_transform};
mod helper;
pub use helper::PhysicsTransformHelper;
#[cfg(test)]
mod tests;
use crate::{
prelude::*,
schedule::{LastPhysicsTick, is_changed_after_tick},
};
use approx::AbsDiffEq;
use bevy::{
ecs::{
change_detection::Tick, intern::Interned, schedule::ScheduleLabel, system::SystemChangeTick,
},
prelude::*,
transform::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms},
};
pub struct PhysicsTransformPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl PhysicsTransformPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for PhysicsTransformPlugin {
fn default() -> Self {
Self::new(FixedPostUpdate)
}
}
impl Plugin for PhysicsTransformPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<PhysicsTransformConfig>();
app.init_resource::<StaticTransformOptimizations>();
if app
.world()
.resource::<PhysicsTransformConfig>()
.position_to_transform
{
app.register_required_components::<Position, Transform>();
app.register_required_components::<Rotation, Transform>();
}
app.configure_sets(
self.schedule,
(
PhysicsTransformSystems::Propagate,
PhysicsTransformSystems::TransformToPosition,
)
.chain()
.in_set(PhysicsSystems::Prepare),
);
app.add_systems(
self.schedule,
(
mark_dirty_trees,
propagate_parent_transforms,
sync_simple_transforms,
)
.chain()
.in_set(PhysicsTransformSystems::Propagate)
.run_if(|config: Res<PhysicsTransformConfig>| config.propagate_before_physics),
);
app.add_systems(
self.schedule,
transform_to_position
.in_set(PhysicsTransformSystems::TransformToPosition)
.run_if(|config: Res<PhysicsTransformConfig>| config.transform_to_position),
);
app.configure_sets(
self.schedule,
PhysicsTransformSystems::PositionToTransform.in_set(PhysicsSystems::Writeback),
);
app.add_systems(
self.schedule,
position_to_transform
.in_set(PhysicsTransformSystems::PositionToTransform)
.run_if(|config: Res<PhysicsTransformConfig>| config.position_to_transform),
);
}
}
#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
#[reflect(Resource)]
pub struct PhysicsTransformConfig {
pub propagate_before_physics: bool,
pub transform_to_position: bool,
pub position_to_transform: bool,
pub transform_to_collider_scale: bool,
}
impl Default for PhysicsTransformConfig {
fn default() -> Self {
PhysicsTransformConfig {
propagate_before_physics: true,
position_to_transform: true,
transform_to_position: true,
transform_to_collider_scale: true,
}
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PhysicsTransformSystems {
Propagate,
TransformToPosition,
PositionToTransform,
}
#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsTransformSystems`")]
pub type PhysicsTransformSet = PhysicsTransformSystems;
#[allow(clippy::type_complexity)]
pub fn transform_to_position(
mut query: Query<(&GlobalTransform, &mut Position, &mut Rotation)>,
length_unit: Res<PhysicsLengthUnit>,
last_physics_tick: Res<LastPhysicsTick>,
system_tick: SystemChangeTick,
) {
let this_run = if last_physics_tick.0.get() == 0 {
Tick::new(1)
} else {
system_tick.this_run()
};
let distance_tolerance = length_unit.0 * 1e-5;
let rotation_tolerance = (0.1 as Scalar).to_radians();
for (global_transform, mut position, mut rotation) in &mut query {
let global_transform = global_transform.compute_transform();
#[cfg(feature = "2d")]
let transform_translation = global_transform.translation.truncate().adjust_precision();
#[cfg(feature = "3d")]
let transform_translation = global_transform.translation.adjust_precision();
let transform_rotation = Rotation::from(global_transform.rotation.adjust_precision());
let position_changed = !position.is_added()
&& is_changed_after_tick(
Ref::from(position.reborrow()),
last_physics_tick.0,
this_run,
);
if !position_changed && position.abs_diff_ne(&transform_translation, distance_tolerance) {
position.0 = transform_translation;
}
let rotation_changed = !rotation.is_added()
&& is_changed_after_tick(
Ref::from(rotation.reborrow()),
last_physics_tick.0,
this_run,
);
if !rotation_changed
&& rotation.angle_between(transform_rotation).abs() > rotation_tolerance
{
*rotation = transform_rotation;
}
}
}
#[derive(Component, Default)]
pub struct ApplyPosToTransform;
type PosToTransformComponents = (
&'static mut Transform,
&'static Position,
&'static Rotation,
Option<&'static ChildOf>,
);
type PosToTransformFilter = (
Or<(With<RigidBody>, With<ApplyPosToTransform>)>,
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(&ChildOf(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(&ChildOf(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();
}
}
}