1#![allow(clippy::type_complexity)]
6
7use crate::{prelude::*, sync::SyncConfig};
8use bevy::{
9 ecs::{intern::Interned, query::QueryFilter, schedule::ScheduleLabel},
10 prelude::*,
11 transform::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms},
12};
13
14pub struct PreparePlugin {
29 schedule: Interned<dyn ScheduleLabel>,
30}
31
32impl PreparePlugin {
33 pub fn new(schedule: impl ScheduleLabel) -> Self {
37 Self {
38 schedule: schedule.intern(),
39 }
40 }
41}
42
43impl Default for PreparePlugin {
44 fn default() -> Self {
45 Self::new(FixedPostUpdate)
46 }
47}
48
49#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub enum PrepareSet {
60 First,
62 PropagateTransforms,
64 InitTransforms,
68 Finalize,
71}
72
73impl Plugin for PreparePlugin {
74 fn build(&self, app: &mut App) {
75 app.init_resource::<SyncConfig>()
76 .register_type::<SyncConfig>();
77 app.configure_sets(
78 self.schedule,
79 (
80 PrepareSet::First,
81 PrepareSet::PropagateTransforms,
82 PrepareSet::InitTransforms,
83 PrepareSet::Finalize,
84 )
85 .chain()
86 .in_set(PhysicsSet::Prepare),
87 );
88
89 app.init_resource::<PrepareConfig>()
90 .register_type::<PrepareConfig>();
91
92 app.add_systems(
94 self.schedule,
95 (
97 mark_dirty_trees,
98 propagate_parent_transforms,
99 sync_simple_transforms,
100 )
101 .chain()
102 .run_if(match_any::<Added<RigidBody>>)
103 .in_set(PrepareSet::PropagateTransforms),
104 )
105 .add_systems(
106 self.schedule,
107 init_transforms::<RigidBody>.in_set(PrepareSet::InitTransforms),
108 );
109 }
110}
111
112#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
114#[reflect(Resource)]
115pub struct PrepareConfig {
116 pub position_to_transform: bool,
119 pub transform_to_position: bool,
122}
123
124impl Default for PrepareConfig {
125 fn default() -> Self {
126 PrepareConfig {
127 position_to_transform: true,
128 transform_to_position: true,
129 }
130 }
131}
132
133pub(crate) fn match_any<F: QueryFilter>(query: Query<(), F>) -> bool {
135 !query.is_empty()
136}
137
138pub fn init_transforms<C: Component>(
141 mut commands: Commands,
142 config: Res<PrepareConfig>,
143 query: Query<
144 (
145 Entity,
146 Option<&Transform>,
147 Option<&GlobalTransform>,
148 Option<&Position>,
149 Option<&Rotation>,
150 Option<&PreviousRotation>,
151 Option<&ChildOf>,
152 Has<RigidBody>,
153 ),
154 Added<C>,
155 >,
156 parents: Query<
157 (
158 Option<&Position>,
159 Option<&Rotation>,
160 Option<&GlobalTransform>,
161 ),
162 With<Children>,
163 >,
164) {
165 if !config.position_to_transform && !config.transform_to_position {
166 return;
168 }
169
170 for (entity, transform, global_transform, pos, rot, previous_rot, parent, has_body) in &query {
171 let parent_transforms = parent.and_then(|&ChildOf(parent)| parents.get(parent).ok());
172 let parent_pos = parent_transforms.and_then(|(pos, _, _)| pos);
173 let parent_rot = parent_transforms.and_then(|(_, rot, _)| rot);
174 let parent_global_trans = parent_transforms.and_then(|(_, _, trans)| trans);
175
176 let mut new_transform = if config.position_to_transform {
177 Some(transform.copied().unwrap_or_default())
178 } else {
179 None
180 };
181
182 let new_position = if let Some(pos) = pos {
184 if let Some(transform) = &mut new_transform {
185 #[cfg(feature = "2d")]
187 let mut new_translation = pos.f32().extend(transform.translation.z);
188 #[cfg(feature = "3d")]
189 let mut new_translation = pos.f32();
190
191 if parent.is_some() {
194 if let Some(parent_pos) = parent_pos {
195 #[cfg(feature = "2d")]
196 {
197 new_translation -= parent_pos.f32().extend(new_translation.z);
198 }
199 #[cfg(feature = "3d")]
200 {
201 new_translation -= parent_pos.f32();
202 }
203 } else if let Some(parent_transform) = parent_global_trans {
204 new_translation -= parent_transform.translation();
205 }
206 }
207 transform.translation = new_translation;
208 }
209 pos.0
210 } else if config.transform_to_position {
211 let mut new_position = Vector::ZERO;
212
213 if parent.is_some() {
214 let translation = transform.as_ref().map_or(default(), |t| t.translation);
215 if let Some(parent_pos) = parent_pos {
216 #[cfg(feature = "2d")]
217 {
218 new_position = parent_pos.0 + translation.adjust_precision().truncate();
219 }
220 #[cfg(feature = "3d")]
221 {
222 new_position = parent_pos.0 + translation.adjust_precision();
223 }
224 } else if let Some(parent_transform) = parent_global_trans {
225 let new_pos = parent_transform
226 .transform_point(transform.as_ref().map_or(default(), |t| t.translation));
227 #[cfg(feature = "2d")]
228 {
229 new_position = new_pos.truncate().adjust_precision();
230 }
231 #[cfg(feature = "3d")]
232 {
233 new_position = new_pos.adjust_precision();
234 }
235 }
236 } else {
237 #[cfg(feature = "2d")]
238 {
239 new_position = transform
240 .map(|t| t.translation.truncate().adjust_precision())
241 .unwrap_or(global_transform.as_ref().map_or(Vector::ZERO, |t| {
242 Vector::new(t.translation().x as Scalar, t.translation().y as Scalar)
243 }));
244 }
245 #[cfg(feature = "3d")]
246 {
247 new_position = transform
248 .map(|t| t.translation.adjust_precision())
249 .unwrap_or(
250 global_transform
251 .as_ref()
252 .map_or(Vector::ZERO, |t| t.translation().adjust_precision()),
253 )
254 }
255 };
256
257 new_position
258 } else {
259 default()
260 };
261
262 let new_rotation = if let Some(rot) = rot {
264 if let Some(transform) = &mut new_transform {
265 let mut new_rotation = Quaternion::from(*rot).f32();
267
268 if parent.is_some() {
271 if let Some(parent_rot) = parent_rot {
272 new_rotation *= Quaternion::from(*parent_rot).f32().inverse();
273 } else if let Some(parent_transform) = parent_global_trans {
274 new_rotation *= parent_transform.compute_transform().rotation.inverse();
275 }
276 }
277 transform.rotation = new_rotation;
278 }
279 *rot
280 } else if config.transform_to_position {
281 if parent.is_some() {
282 let parent_rot = parent_rot.copied().unwrap_or(Rotation::from(
283 parent_global_trans.map_or(default(), |t| t.compute_transform().rotation),
284 ));
285 let rot = Rotation::from(transform.as_ref().map_or(default(), |t| t.rotation));
286 #[cfg(feature = "2d")]
287 {
288 parent_rot * rot
289 }
290 #[cfg(feature = "3d")]
291 {
292 Rotation(parent_rot.0 * rot.0)
293 }
294 } else {
295 transform.map(|t| Rotation::from(t.rotation)).unwrap_or(
296 global_transform.map_or(Rotation::default(), |t| {
297 t.compute_transform().rotation.into()
298 }),
299 )
300 }
301 } else {
302 default()
303 };
304
305 let mut cmds = commands.entity(entity);
306
307 match (has_body, new_transform) {
313 (true, None) => {
314 cmds.try_insert((
315 Position(new_position),
316 new_rotation,
317 PreSolveAccumulatedTranslation::default(),
318 *previous_rot.unwrap_or(&PreviousRotation(new_rotation)),
319 PreSolveRotation::default(),
320 ));
321 }
322 (true, Some(transform)) => {
323 cmds.try_insert((
324 transform,
325 Position(new_position),
326 new_rotation,
327 PreSolveAccumulatedTranslation::default(),
328 *previous_rot.unwrap_or(&PreviousRotation(new_rotation)),
329 PreSolveRotation::default(),
330 ));
331 }
332 (false, None) => {
333 cmds.try_insert((Position(new_position), new_rotation));
334 }
335 (false, Some(transform)) => {
336 cmds.try_insert((transform, Position(new_position), new_rotation));
337 }
338 }
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_init_transforms_basics() {
348 let mut app = App::new();
349
350 app.add_systems(Update, init_transforms::<RigidBody>);
352
353 for (position_to_transform, transform_to_position) in
355 [(true, true), (true, false), (false, true), (false, false)]
356 {
357 let config = PrepareConfig {
358 position_to_transform,
359 transform_to_position,
360 };
361 app.insert_resource(dbg!(config.clone()));
362
363 let (pos_0, rot_0) = {
365 #[cfg(feature = "2d")]
366 {
367 (Position::from_xy(1., 2.), Rotation::radians(0.5))
368 }
369 #[cfg(feature = "3d")]
370 {
371 (
372 Position::from_xyz(1., 2., 3.),
373 Rotation(Quaternion::from_axis_angle(Vector::Y, 0.5)),
374 )
375 }
376 };
377 let e_0_with_pos_and_rot = app
378 .world_mut()
379 .spawn((RigidBody::Dynamic, pos_0, rot_0))
380 .id();
381
382 let (pos_1, rot_1) = {
383 #[cfg(feature = "2d")]
384 {
385 (Position::from_xy(-1., 3.), Rotation::radians(0.1))
386 }
387 #[cfg(feature = "3d")]
388 {
389 (
390 Position::from_xyz(-1., 3., -3.),
391 Rotation(Quaternion::from_axis_angle(Vector::X, 0.1)),
392 )
393 }
394 };
395 let e_1_with_pos_and_rot = app
396 .world_mut()
397 .spawn((RigidBody::Dynamic, pos_1, rot_1))
398 .id();
399
400 let pos_2 = {
402 #[cfg(feature = "2d")]
403 {
404 Position::from_xy(10., 1.)
405 }
406 #[cfg(feature = "3d")]
407 {
408 Position::from_xyz(10., 1., 5.)
409 }
410 };
411 let e_2_with_pos = app.world_mut().spawn((RigidBody::Dynamic, pos_2)).id();
412
413 let rot_3 = {
415 #[cfg(feature = "2d")]
416 {
417 Rotation::radians(0.4)
418 }
419 #[cfg(feature = "3d")]
420 {
421 Rotation(Quaternion::from_axis_angle(Vector::Z, 0.4))
422 }
423 };
424 let e_3_with_rot = app.world_mut().spawn((RigidBody::Dynamic, rot_3)).id();
425
426 let trans_4 = {
428 Transform {
429 translation: Vec3::new(-1.1, 6., -7.),
430 rotation: Quat::from_axis_angle(Vec3::Y, 0.1),
431 scale: Vec3::ONE,
432 }
433 };
434 let e_4_with_trans = app.world_mut().spawn((RigidBody::Dynamic, trans_4)).id();
435
436 let trans_5 = {
437 Transform {
438 translation: Vec3::new(8., -1., 0.),
439 rotation: Quat::from_axis_angle(Vec3::Y, -0.1),
440 scale: Vec3::ONE,
441 }
442 };
443 let e_5_with_trans = app.world_mut().spawn((RigidBody::Dynamic, trans_5)).id();
444
445 let e_6_without_trans = app.world_mut().spawn(RigidBody::Dynamic).id();
447
448 let e_7_without_rb = app.world_mut().spawn(()).id();
450
451 app.update();
453
454 if config.position_to_transform {
456 assert!(app.world().get::<Transform>(e_0_with_pos_and_rot).is_some());
457 let transform = app.world().get::<Transform>(e_0_with_pos_and_rot).unwrap();
458 let expected: Vec3 = {
459 #[cfg(feature = "2d")]
460 {
461 pos_0.f32().extend(0.)
462 }
463 #[cfg(feature = "3d")]
464 {
465 pos_0.f32()
466 }
467 };
468 assert_eq!(transform.translation, expected);
469 let expected = Quaternion::from(rot_0).f32();
470 assert_eq!(transform.rotation, expected);
471
472 assert!(app.world().get::<Transform>(e_1_with_pos_and_rot).is_some());
473 let transform = app.world().get::<Transform>(e_1_with_pos_and_rot).unwrap();
474 let expected: Vec3 = {
475 #[cfg(feature = "2d")]
476 {
477 pos_1.f32().extend(0.)
478 }
479 #[cfg(feature = "3d")]
480 {
481 pos_1.f32()
482 }
483 };
484 assert_eq!(transform.translation, expected);
485 let expected = Quaternion::from(rot_1).f32();
486 assert_eq!(transform.rotation, expected);
487
488 assert!(app.world().get::<Transform>(e_2_with_pos).is_some());
489 let transform = app.world().get::<Transform>(e_2_with_pos).unwrap();
490 let expected: Vec3 = {
491 #[cfg(feature = "2d")]
492 {
493 pos_2.f32().extend(0.)
494 }
495 #[cfg(feature = "3d")]
496 {
497 pos_2.f32()
498 }
499 };
500 assert_eq!(transform.translation, expected);
501 let expected = Quat::default();
502 assert_eq!(transform.rotation, expected);
503
504 assert!(app.world().get::<Transform>(e_3_with_rot).is_some());
505 let transform = app.world().get::<Transform>(e_3_with_rot).unwrap();
506 let expected: Vec3 = Vec3::default();
507 assert_eq!(transform.translation, expected);
508 let expected = Quaternion::from(rot_3).f32();
509 assert_eq!(transform.rotation, expected);
510
511 assert!(app.world().get::<Transform>(e_4_with_trans).is_some());
512 let transform = app.world().get::<Transform>(e_4_with_trans).unwrap();
513 assert_eq!(transform, &trans_4);
514
515 assert!(app.world().get::<Transform>(e_5_with_trans).is_some());
516 let transform = app.world().get::<Transform>(e_5_with_trans).unwrap();
517 assert_eq!(transform, &trans_5);
518
519 assert!(app.world().get::<Transform>(e_6_without_trans).is_some());
520 let transform = app.world().get::<Transform>(e_6_without_trans).unwrap();
521 assert_eq!(transform, &Transform::default());
522
523 assert!(app.world().get::<Transform>(e_7_without_rb).is_none());
524 }
525
526 if config.transform_to_position {
527 assert!(app.world().get::<Position>(e_0_with_pos_and_rot).is_some());
528 let pos = app.world().get::<Position>(e_0_with_pos_and_rot).unwrap();
529 assert_eq!(pos, &pos_0);
530 assert!(app.world().get::<Rotation>(e_0_with_pos_and_rot).is_some());
531 let rot = app.world().get::<Rotation>(e_0_with_pos_and_rot).unwrap();
532 assert_eq!(rot, &rot_0);
533
534 assert!(app.world().get::<Position>(e_1_with_pos_and_rot).is_some());
535 let pos = app.world().get::<Position>(e_1_with_pos_and_rot).unwrap();
536 assert_eq!(pos, &pos_1);
537 assert!(app.world().get::<Rotation>(e_1_with_pos_and_rot).is_some());
538 let rot = app.world().get::<Rotation>(e_1_with_pos_and_rot).unwrap();
539 assert_eq!(rot, &rot_1);
540
541 assert!(app.world().get::<Position>(e_2_with_pos).is_some());
542 let pos = app.world().get::<Position>(e_2_with_pos).unwrap();
543 assert_eq!(pos, &pos_2);
544 assert!(app.world().get::<Rotation>(e_2_with_pos).is_some());
545 let rot = app.world().get::<Rotation>(e_2_with_pos).unwrap();
546 assert_eq!(rot, &Rotation::default());
547
548 assert!(app.world().get::<Position>(e_3_with_rot).is_some());
549 let pos = app.world().get::<Position>(e_3_with_rot).unwrap();
550 assert_eq!(pos, &Position::default());
551 assert!(app.world().get::<Rotation>(e_3_with_rot).is_some());
552 let rot = app.world().get::<Rotation>(e_3_with_rot).unwrap();
553 assert_eq!(rot, &rot_3);
554
555 assert!(app.world().get::<Position>(e_4_with_trans).is_some());
556 let pos = app.world().get::<Position>(e_4_with_trans).unwrap();
557 let expected: Position = Position::new({
558 #[cfg(feature = "2d")]
559 {
560 trans_4.translation.truncate().adjust_precision()
561 }
562 #[cfg(feature = "3d")]
563 {
564 trans_4.translation.adjust_precision()
565 }
566 });
567 assert_eq!(pos, &expected);
568 assert!(app.world().get::<Rotation>(e_4_with_trans).is_some());
569 let rot = app.world().get::<Rotation>(e_4_with_trans).unwrap();
570 assert_eq!(rot, &Rotation::from(trans_4.rotation));
571
572 assert!(app.world().get::<Position>(e_5_with_trans).is_some());
573 let pos = app.world().get::<Position>(e_5_with_trans).unwrap();
574 let expected: Position = Position::new({
575 #[cfg(feature = "2d")]
576 {
577 trans_5.translation.truncate().adjust_precision()
578 }
579 #[cfg(feature = "3d")]
580 {
581 trans_5.translation.adjust_precision()
582 }
583 });
584 assert_eq!(pos, &expected);
585 assert!(app.world().get::<Rotation>(e_5_with_trans).is_some());
586 let rot = app.world().get::<Rotation>(e_5_with_trans).unwrap();
587 assert_eq!(rot, &Rotation::from(trans_5.rotation));
588
589 assert!(app.world().get::<Position>(e_6_without_trans).is_some());
590 let pos = app.world().get::<Position>(e_6_without_trans).unwrap();
591 assert_eq!(pos, &Position::default());
592 assert!(app.world().get::<Rotation>(e_6_without_trans).is_some());
593 let rot = app.world().get::<Rotation>(e_6_without_trans).unwrap();
594 assert_eq!(rot, &Rotation::default());
595
596 assert!(app.world().get::<Position>(e_7_without_rb).is_none());
597 assert!(app.world().get::<Rotation>(e_7_without_rb).is_none());
598 }
599 }
600 }
601}