avian2d/physics_transform/
mod.rs1mod transform;
6pub use transform::{Position, PreSolveDeltaPosition, PreSolveDeltaRotation, Rotation};
7#[allow(unused_imports)]
8pub(crate) use transform::{RotationValue, init_physics_transform};
9
10mod helper;
11pub use helper::PhysicsTransformHelper;
12
13#[cfg(test)]
14mod tests;
15
16use crate::{
17 prelude::*,
18 schedule::{LastPhysicsTick, is_changed_after_tick},
19};
20use approx::AbsDiffEq;
21use bevy::{
22 ecs::{
23 change_detection::Tick, intern::Interned, schedule::ScheduleLabel, system::SystemChangeTick,
24 },
25 prelude::*,
26 transform::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms},
27};
28
29pub struct PhysicsTransformPlugin {
49 schedule: Interned<dyn ScheduleLabel>,
50}
51
52impl PhysicsTransformPlugin {
53 pub fn new(schedule: impl ScheduleLabel) -> Self {
57 Self {
58 schedule: schedule.intern(),
59 }
60 }
61}
62
63impl Default for PhysicsTransformPlugin {
64 fn default() -> Self {
65 Self::new(FixedPostUpdate)
66 }
67}
68
69impl Plugin for PhysicsTransformPlugin {
70 fn build(&self, app: &mut App) {
71 app.init_resource::<PhysicsTransformConfig>();
72
73 app.init_resource::<StaticTransformOptimizations>();
75
76 if app
77 .world()
78 .resource::<PhysicsTransformConfig>()
79 .position_to_transform
80 {
81 app.register_required_components::<Position, Transform>();
82 app.register_required_components::<Rotation, Transform>();
83 }
84
85 app.configure_sets(
87 self.schedule,
88 (
89 PhysicsTransformSystems::Propagate,
90 PhysicsTransformSystems::TransformToPosition,
91 )
92 .chain()
93 .in_set(PhysicsSystems::Prepare),
94 );
95 app.add_systems(
96 self.schedule,
97 (
98 mark_dirty_trees,
99 propagate_parent_transforms,
100 sync_simple_transforms,
101 )
102 .chain()
103 .in_set(PhysicsTransformSystems::Propagate)
104 .run_if(|config: Res<PhysicsTransformConfig>| config.propagate_before_physics),
105 );
106 app.add_systems(
107 self.schedule,
108 transform_to_position
109 .in_set(PhysicsTransformSystems::TransformToPosition)
110 .run_if(|config: Res<PhysicsTransformConfig>| config.transform_to_position),
111 );
112
113 app.configure_sets(
115 self.schedule,
116 PhysicsTransformSystems::PositionToTransform.in_set(PhysicsSystems::Writeback),
117 );
118 app.add_systems(
119 self.schedule,
120 position_to_transform
121 .in_set(PhysicsTransformSystems::PositionToTransform)
122 .run_if(|config: Res<PhysicsTransformConfig>| config.position_to_transform),
123 );
124 }
125}
126
127#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
129#[reflect(Resource)]
130pub struct PhysicsTransformConfig {
131 pub propagate_before_physics: bool,
136 pub transform_to_position: bool,
143 pub position_to_transform: bool,
148 pub transform_to_collider_scale: bool,
154}
155
156impl Default for PhysicsTransformConfig {
157 fn default() -> Self {
158 PhysicsTransformConfig {
159 propagate_before_physics: true,
160 position_to_transform: true,
161 transform_to_position: true,
162 transform_to_collider_scale: true,
163 }
164 }
165}
166
167#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
169pub enum PhysicsTransformSystems {
170 Propagate,
172 TransformToPosition,
174 PositionToTransform,
176}
177
178#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsTransformSystems`")]
180pub type PhysicsTransformSet = PhysicsTransformSystems;
181
182#[allow(clippy::type_complexity)]
187pub fn transform_to_position(
188 mut query: Query<(&GlobalTransform, &mut Position, &mut Rotation)>,
189 length_unit: Res<PhysicsLengthUnit>,
190 last_physics_tick: Res<LastPhysicsTick>,
191 system_tick: SystemChangeTick,
192) {
193 let this_run = if last_physics_tick.0.get() == 0 {
197 Tick::new(1)
198 } else {
199 system_tick.this_run()
200 };
201
202 let distance_tolerance = length_unit.0 * 1e-5;
204 let rotation_tolerance = (0.1 as Scalar).to_radians();
206
207 for (global_transform, mut position, mut rotation) in &mut query {
208 let global_transform = global_transform.compute_transform();
209 #[cfg(feature = "2d")]
210 let transform_translation = global_transform.translation.truncate().adjust_precision();
211 #[cfg(feature = "3d")]
212 let transform_translation = global_transform.translation.adjust_precision();
213 let transform_rotation = Rotation::from(global_transform.rotation.adjust_precision());
214
215 let position_changed = !position.is_added()
216 && is_changed_after_tick(
217 Ref::from(position.reborrow()),
218 last_physics_tick.0,
219 this_run,
220 );
221 if !position_changed && position.abs_diff_ne(&transform_translation, distance_tolerance) {
222 position.0 = transform_translation;
223 }
224
225 let rotation_changed = !rotation.is_added()
226 && is_changed_after_tick(
227 Ref::from(rotation.reborrow()),
228 last_physics_tick.0,
229 this_run,
230 );
231 if !rotation_changed
232 && rotation.angle_between(transform_rotation).abs() > rotation_tolerance
233 {
234 *rotation = transform_rotation;
235 }
236 }
237}
238
239#[derive(Component, Default)]
245pub struct ApplyPosToTransform;
246
247type PosToTransformComponents = (
248 &'static mut Transform,
249 &'static Position,
250 &'static Rotation,
251 Option<&'static ChildOf>,
252);
253
254type PosToTransformFilter = (
255 Or<(With<RigidBody>, With<ApplyPosToTransform>)>,
256 Or<(Changed<Position>, Changed<Rotation>)>,
257);
258
259type ParentComponents = (
260 &'static GlobalTransform,
261 Option<&'static Position>,
262 Option<&'static Rotation>,
263);
264
265#[cfg(feature = "2d")]
271pub fn position_to_transform(
272 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
273 parents: Query<ParentComponents, With<Children>>,
274) {
275 for (mut transform, pos, rot, parent) in &mut query {
276 if let Some(&ChildOf(parent)) = parent {
277 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
278 let parent_transform = parent_transform.compute_transform();
280 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| {
281 pos.f32().extend(parent_transform.translation.z)
282 });
283 let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| {
284 Quaternion::from(*rot).f32()
285 });
286 let parent_scale = parent_transform.scale;
287 let parent_transform = Transform::from_translation(parent_pos)
288 .with_rotation(parent_rot)
289 .with_scale(parent_scale);
290
291 let new_transform = GlobalTransform::from(
294 Transform::from_translation(
295 pos.f32()
296 .extend(parent_pos.z + transform.translation.z * parent_scale.z),
297 )
298 .with_rotation(Quaternion::from(*rot).f32()),
299 )
300 .reparented_to(&GlobalTransform::from(parent_transform));
301
302 transform.translation = new_transform.translation;
303 transform.rotation = new_transform.rotation;
304 }
305 } else {
306 transform.translation = pos.f32().extend(transform.translation.z);
307 transform.rotation = Quaternion::from(*rot).f32();
308 }
309 }
310}
311
312#[cfg(feature = "3d")]
318pub fn position_to_transform(
319 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
320 parents: Query<ParentComponents, With<Children>>,
321) {
322 for (mut transform, pos, rot, parent) in &mut query {
323 if let Some(&ChildOf(parent)) = parent {
324 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
325 let parent_transform = parent_transform.compute_transform();
327 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| pos.f32());
328 let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| rot.f32());
329 let parent_scale = parent_transform.scale;
330 let parent_transform = Transform::from_translation(parent_pos)
331 .with_rotation(parent_rot)
332 .with_scale(parent_scale);
333
334 let new_transform = GlobalTransform::from(
337 Transform::from_translation(pos.f32()).with_rotation(rot.f32()),
338 )
339 .reparented_to(&GlobalTransform::from(parent_transform));
340
341 transform.translation = new_transform.translation;
342 transform.rotation = new_transform.rotation;
343 }
344 } else {
345 transform.translation = pos.f32();
346 transform.rotation = rot.f32();
347 }
348 }
349}