avian3d/physics_transform/
mod.rs1mod transform;
6#[allow(unused_imports)]
7pub(crate) use transform::init_physics_transform;
8pub use transform::{Position, PreSolveDeltaPosition, PreSolveDeltaRotation, Rotation};
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 utils::{MIN_PAR_ITER_ENTITIES, ParallelQueryForEach},
20};
21use approx::AbsDiffEq;
22use bevy::{
23 ecs::{
24 change_detection::Tick, intern::Interned, schedule::ScheduleLabel, system::SystemChangeTick,
25 },
26 math::Affine3A,
27 prelude::*,
28 transform::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms},
29};
30
31pub struct PhysicsTransformPlugin {
51 schedule: Interned<dyn ScheduleLabel>,
52}
53
54impl PhysicsTransformPlugin {
55 pub fn new(schedule: impl ScheduleLabel) -> Self {
59 Self {
60 schedule: schedule.intern(),
61 }
62 }
63}
64
65impl Default for PhysicsTransformPlugin {
66 fn default() -> Self {
67 Self::new(FixedPostUpdate)
68 }
69}
70
71impl Plugin for PhysicsTransformPlugin {
72 fn build(&self, app: &mut App) {
73 app.init_resource::<PhysicsTransformConfig>();
74
75 app.init_resource::<StaticTransformOptimizations>();
77
78 if app
79 .world()
80 .resource::<PhysicsTransformConfig>()
81 .position_to_transform
82 {
83 app.register_required_components::<Position, Transform>();
84 app.register_required_components::<Rotation, Transform>();
85 }
86
87 app.configure_sets(
89 self.schedule,
90 (
91 PhysicsTransformSystems::Propagate,
92 PhysicsTransformSystems::TransformToPosition,
93 )
94 .chain()
95 .in_set(PhysicsSystems::Prepare),
96 );
97 app.add_systems(
98 self.schedule,
99 (
100 mark_dirty_trees,
101 propagate_parent_transforms,
102 sync_simple_transforms,
103 )
104 .chain()
105 .in_set(PhysicsTransformSystems::Propagate)
106 .run_if(|config: Res<PhysicsTransformConfig>| config.propagate_before_physics),
107 );
108 app.add_systems(
109 self.schedule,
110 transform_to_position
111 .in_set(PhysicsTransformSystems::TransformToPosition)
112 .run_if(|config: Res<PhysicsTransformConfig>| config.transform_to_position),
113 );
114
115 app.configure_sets(
117 self.schedule,
118 PhysicsTransformSystems::PositionToTransform.in_set(PhysicsSystems::Writeback),
119 );
120 app.add_systems(
121 self.schedule,
122 position_to_transform
123 .in_set(PhysicsTransformSystems::PositionToTransform)
124 .run_if(|config: Res<PhysicsTransformConfig>| config.position_to_transform),
125 );
126 }
127}
128
129#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
131#[reflect(Resource)]
132pub struct PhysicsTransformConfig {
133 pub propagate_before_physics: bool,
138 pub transform_to_position: bool,
145 pub position_to_transform: bool,
150 pub transform_to_collider_scale: bool,
156}
157
158impl Default for PhysicsTransformConfig {
159 fn default() -> Self {
160 PhysicsTransformConfig {
161 propagate_before_physics: true,
162 position_to_transform: true,
163 transform_to_position: true,
164 transform_to_collider_scale: true,
165 }
166 }
167}
168
169#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
171pub enum PhysicsTransformSystems {
172 Propagate,
174 TransformToPosition,
176 PositionToTransform,
178}
179
180#[deprecated(since = "0.4.0", note = "Renamed to `PhysicsTransformSystems`")]
182pub type PhysicsTransformSet = PhysicsTransformSystems;
183
184#[allow(clippy::type_complexity)]
189pub fn transform_to_position(
190 mut query: Query<(Ref<GlobalTransform>, &mut Position, &mut Rotation)>,
191 length_unit: Res<PhysicsLengthUnit>,
192 last_physics_tick: Res<LastPhysicsTick>,
193 system_tick: SystemChangeTick,
194) {
195 let this_run = if last_physics_tick.0.get() == 0 {
199 Tick::new(1)
200 } else {
201 system_tick.this_run()
202 };
203
204 let distance_tolerance = length_unit.real() * 1e-5;
206
207 let last_physics_tick = last_physics_tick.0;
208
209 query.par_for_each_mut(
210 MIN_PAR_ITER_ENTITIES,
211 |(global_transform, mut position, mut rotation)| {
212 let transform_changed = global_transform.is_added()
213 || is_changed_after_tick(global_transform, last_physics_tick, this_run);
214 if !transform_changed {
215 return;
216 }
217
218 let affine = global_transform.affine();
219
220 let position_changed = !position.is_added()
221 && is_changed_after_tick(
222 Ref::from(position.reborrow()),
223 last_physics_tick,
224 this_run,
225 );
226 if !position_changed {
227 #[cfg(feature = "2d")]
228 let transform_translation = affine.translation.truncate().real();
229 #[cfg(feature = "3d")]
230 let transform_translation = Vec3::from(affine.translation).real();
231
232 if position.abs_diff_ne(&transform_translation, distance_tolerance) {
233 position.0 = transform_translation;
234 }
235 }
236
237 let rotation_changed = !rotation.is_added()
238 && is_changed_after_tick(
239 Ref::from(rotation.reborrow()),
240 last_physics_tick,
241 this_run,
242 );
243 if !rotation_changed {
244 let transform_rotation = rotation_from_affine(&affine);
245 if cos_angle_between(*rotation, transform_rotation) < ROTATION_COS_TOLERANCE {
248 *rotation = transform_rotation;
249 }
250 }
251 },
252 );
253}
254
255const ROTATION_COS_TOLERANCE: f32 = 0.999_998_5;
258
259#[inline]
264fn cos_angle_between(a: Rotation, b: Rotation) -> f32 {
265 #[cfg(feature = "2d")]
266 {
267 a.cos * b.cos + a.sin * b.sin
268 }
269 #[cfg(feature = "3d")]
270 {
271 let dot = a.dot(b.0);
274 2.0 * dot * dot - 1.0
275 }
276}
277
278#[inline]
283fn rotation_from_affine(affine: &Affine3A) -> Rotation {
284 let mat = affine.matrix3;
285
286 let det_sign = mat.determinant().signum();
287
288 #[cfg(feature = "2d")]
289 {
290 let x_axis = Vec2::new(mat.x_axis.x, mat.x_axis.y) * det_sign;
291 let x_axis = x_axis.normalize_or(Vec2::X);
292 Rotation {
293 cos: x_axis.x,
294 sin: x_axis.y,
295 }
296 }
297 #[cfg(feature = "3d")]
298 {
299 let x_axis = (mat.x_axis * det_sign).normalize_or(Vec3A::X);
300 let y_axis = mat.y_axis.normalize_or(Vec3A::Y);
301 let z_axis = mat.z_axis.normalize_or(Vec3A::Z);
302 Rotation(Quat::from_mat3a(&Mat3A::from_cols(x_axis, y_axis, z_axis)))
303 }
304}
305
306#[derive(Component, Default)]
312pub struct ApplyPosToTransform;
313
314type PosToTransformComponents = (
315 &'static mut Transform,
316 &'static Position,
317 &'static Rotation,
318 Option<&'static ChildOf>,
319);
320
321type PosToTransformFilter = (
322 Or<(With<RigidBody>, With<ApplyPosToTransform>)>,
323 Or<(Changed<Position>, Changed<Rotation>)>,
324);
325
326type ParentComponents = (
327 &'static GlobalTransform,
328 Option<&'static Position>,
329 Option<&'static Rotation>,
330);
331
332#[cfg(feature = "2d")]
338pub fn position_to_transform(
339 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
340 parents: Query<ParentComponents, With<Children>>,
341) {
342 for (mut transform, pos, rot, parent) in &mut query {
343 if let Some(&ChildOf(parent)) = parent {
344 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
345 let parent_transform = parent_transform.compute_transform();
347 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| {
348 pos.f32().extend(parent_transform.translation.z)
349 });
350 let parent_rot =
351 parent_rot.map_or(parent_transform.rotation, |rot| Quat::from(*rot));
352 let parent_scale = parent_transform.scale;
353 let parent_transform = Transform::from_translation(parent_pos)
354 .with_rotation(parent_rot)
355 .with_scale(parent_scale);
356
357 let new_transform = GlobalTransform::from(
360 Transform::from_translation(
361 pos.f32()
362 .extend(parent_pos.z + transform.translation.z * parent_scale.z),
363 )
364 .with_rotation(Quat::from(*rot)),
365 )
366 .reparented_to(&GlobalTransform::from(parent_transform));
367
368 transform.translation = new_transform.translation;
369 transform.rotation = new_transform.rotation;
370 }
371 } else {
372 transform.translation = pos.f32().extend(transform.translation.z);
373 transform.rotation = Quat::from(*rot);
374 }
375 }
376}
377
378#[cfg(feature = "3d")]
384pub fn position_to_transform(
385 mut query: Query<PosToTransformComponents, PosToTransformFilter>,
386 parents: Query<ParentComponents, With<Children>>,
387) {
388 for (mut transform, pos, rot, parent) in &mut query {
389 if let Some(&ChildOf(parent)) = parent {
390 if let Ok((parent_transform, parent_pos, parent_rot)) = parents.get(parent) {
391 let parent_transform = parent_transform.compute_transform();
393 let parent_pos = parent_pos.map_or(parent_transform.translation, |pos| pos.f32());
394 let parent_rot = parent_rot.map_or(parent_transform.rotation, |rot| rot.0);
395 let parent_scale = parent_transform.scale;
396 let parent_transform = Transform::from_translation(parent_pos)
397 .with_rotation(parent_rot)
398 .with_scale(parent_scale);
399
400 let new_transform = GlobalTransform::from(
403 Transform::from_translation(pos.f32()).with_rotation(rot.0),
404 )
405 .reparented_to(&GlobalTransform::from(parent_transform));
406
407 transform.translation = new_transform.translation;
408 transform.rotation = new_transform.rotation;
409 }
410 } else {
411 transform.translation = pos.f32();
412 transform.rotation = rot.0;
413 }
414 }
415}