avian3d/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::{component::Tick, intern::Interned, schedule::ScheduleLabel, system::SystemChangeTick},
23 prelude::*,
24 transform::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms},
25};
26
27pub struct PhysicsTransformPlugin {
47 schedule: Interned<dyn ScheduleLabel>,
48}
49
50impl PhysicsTransformPlugin {
51 pub fn new(schedule: impl ScheduleLabel) -> Self {
55 Self {
56 schedule: schedule.intern(),
57 }
58 }
59}
60
61impl Default for PhysicsTransformPlugin {
62 fn default() -> Self {
63 Self::new(FixedPostUpdate)
64 }
65}
66
67impl Plugin for PhysicsTransformPlugin {
68 fn build(&self, app: &mut App) {
69 app.init_resource::<PhysicsTransformConfig>();
70
71 if app
72 .world()
73 .resource::<PhysicsTransformConfig>()
74 .position_to_transform
75 {
76 app.register_required_components::<Position, Transform>();
77 app.register_required_components::<Rotation, Transform>();
78 }
79
80 app.configure_sets(
82 self.schedule,
83 (
84 PhysicsTransformSystems::Propagate,
85 PhysicsTransformSystems::TransformToPosition,
86 )
87 .chain()
88 .in_set(PhysicsSystems::Prepare),
89 );
90 app.add_systems(
91 self.schedule,
92 (
93 mark_dirty_trees,
94 propagate_parent_transforms,
95 sync_simple_transforms,
96 )
97 .chain()
98 .in_set(PhysicsTransformSystems::Propagate)
99 .run_if(|config: Res<PhysicsTransformConfig>| config.propagate_before_physics),
100 );
101 app.add_systems(
102 self.schedule,
103 transform_to_position
104 .in_set(PhysicsTransformSystems::TransformToPosition)
105 .run_if(|config: Res<PhysicsTransformConfig>| config.transform_to_position),
106 );
107
108 app.configure_sets(
110 self.schedule,
111 PhysicsTransformSystems::PositionToTransform.in_set(PhysicsSystems::Writeback),
112 );
113 app.add_systems(
114 self.schedule,
115 position_to_transform
116 .in_set(PhysicsTransformSystems::PositionToTransform)
117 .run_if(|config: Res<PhysicsTransformConfig>| config.position_to_transform),
118 );
119 }
120}
121
122#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
124#[reflect(Resource)]
125pub struct PhysicsTransformConfig {
126 pub propagate_before_physics: bool,
131 pub transform_to_position: bool,
138 pub position_to_transform: bool,
143 pub transform_to_collider_scale: bool,
149}
150
151impl Default for PhysicsTransformConfig {
152 fn default() -> Self {
153 PhysicsTransformConfig {
154 propagate_before_physics: true,
155 position_to_transform: true,
156 transform_to_position: true,
157 transform_to_collider_scale: true,
158 }
159 }
160}
161
162#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
164pub enum PhysicsTransformSystems {
165 Propagate,
167 TransformToPosition,
169 PositionToTransform,
171}
172
173#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsTransformSystems`")]
175pub type PhysicsTransformSet = PhysicsTransformSystems;
176
177#[allow(clippy::type_complexity)]
182pub fn transform_to_position(
183 mut query: Query<(&GlobalTransform, &mut Position, &mut Rotation)>,
184 length_unit: Res<PhysicsLengthUnit>,
185 last_physics_tick: Res<LastPhysicsTick>,
186 system_tick: SystemChangeTick,
187) {
188 let this_run = if last_physics_tick.0.get() == 0 {
192 Tick::new(1)
193 } else {
194 system_tick.this_run()
195 };
196
197 let distance_tolerance = length_unit.0 * 1e-5;
199 let rotation_tolerance = (0.1 as Scalar).to_radians();
201
202 for (global_transform, mut position, mut rotation) in &mut query {
203 let global_transform = global_transform.compute_transform();
204 #[cfg(feature = "2d")]
205 let transform_translation = global_transform.translation.truncate().adjust_precision();
206 #[cfg(feature = "3d")]
207 let transform_translation = global_transform.translation.adjust_precision();
208 let transform_rotation = Rotation::from(global_transform.rotation.adjust_precision());
209
210 let position_changed = is_changed_after_tick(
211 Ref::from(position.reborrow()),
212 last_physics_tick.0,
213 this_run,
214 );
215 if !position_changed && position.abs_diff_ne(&transform_translation, distance_tolerance) {
216 position.0 = transform_translation;
217 }
218
219 let rotation_changed = is_changed_after_tick(
220 Ref::from(rotation.reborrow()),
221 last_physics_tick.0,
222 this_run,
223 );
224 if !rotation_changed
225 && rotation.angle_between(transform_rotation).abs() > rotation_tolerance
226 {
227 *rotation = transform_rotation;
228 }
229 }
230}
231
232#[derive(Component, Default)]
238pub struct ApplyPosToTransform;
239
240type PosToTransformComponents = (
241 &'static mut Transform,
242 &'static Position,
243 &'static Rotation,
244 Option<&'static ChildOf>,
245);
246
247type PosToTransformFilter = (
248 Or<(With<RigidBody>, With<ApplyPosToTransform>)>,
249 Or<(Changed<Position>, Changed<Rotation>)>,
250);
251
252type ParentComponents = (
253 &'static GlobalTransform,
254 Option<&'static Position>,
255 Option<&'static Rotation>,
256);
257
258#[cfg(feature = "2d")]
264pub fn position_to_transform(
265 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
266 parents: Query<ParentComponents, With<Children>>,
267) {
268 for (mut transform, pos, rot, parent) in &mut query {
269 if let Some(&ChildOf(parent)) = parent {
270 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
271 let parent_transform = parent_transform.compute_transform();
273 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| {
274 pos.f32().extend(parent_transform.translation.z)
275 });
276 let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| {
277 Quaternion::from(*rot).f32()
278 });
279 let parent_scale = parent_transform.scale;
280 let parent_transform = Transform::from_translation(parent_pos)
281 .with_rotation(parent_rot)
282 .with_scale(parent_scale);
283
284 let new_transform = GlobalTransform::from(
287 Transform::from_translation(
288 pos.f32()
289 .extend(parent_pos.z + transform.translation.z * parent_scale.z),
290 )
291 .with_rotation(Quaternion::from(*rot).f32()),
292 )
293 .reparented_to(&GlobalTransform::from(parent_transform));
294
295 transform.translation = new_transform.translation;
296 transform.rotation = new_transform.rotation;
297 }
298 } else {
299 transform.translation = pos.f32().extend(transform.translation.z);
300 transform.rotation = Quaternion::from(*rot).f32();
301 }
302 }
303}
304
305#[cfg(feature = "3d")]
311pub fn position_to_transform(
312 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
313 parents: Query<ParentComponents, With<Children>>,
314) {
315 for (mut transform, pos, rot, parent) in &mut query {
316 if let Some(&ChildOf(parent)) = parent {
317 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
318 let parent_transform = parent_transform.compute_transform();
320 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| pos.f32());
321 let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| rot.f32());
322 let parent_scale = parent_transform.scale;
323 let parent_transform = Transform::from_translation(parent_pos)
324 .with_rotation(parent_rot)
325 .with_scale(parent_scale);
326
327 let new_transform = GlobalTransform::from(
330 Transform::from_translation(pos.f32()).with_rotation(rot.f32()),
331 )
332 .reparented_to(&GlobalTransform::from(parent_transform));
333
334 transform.translation = new_transform.translation;
335 transform.rotation = new_transform.rotation;
336 }
337 } else {
338 transform.translation = pos.f32();
339 transform.rotation = rot.f32();
340 }
341 }
342}