1#![allow(clippy::type_complexity)]
7
8use crate::prelude::*;
9use bevy::{
10 ecs::{intern::Interned, query::QueryFilter},
11 prelude::*,
12};
13
14pub struct PreparePlugin {
31 schedule: Interned<dyn ScheduleLabel>,
32}
33
34impl PreparePlugin {
35 pub fn new(schedule: impl ScheduleLabel) -> Self {
39 Self {
40 schedule: schedule.intern(),
41 }
42 }
43}
44
45impl Default for PreparePlugin {
46 fn default() -> Self {
47 Self::new(PostUpdate)
48 }
49}
50
51#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub enum PrepareSet {
65 PreInit,
67 InitRigidBodies,
69 InitColliders,
71 PropagateTransforms,
73 InitMassProperties,
75 InitTransforms,
79 Finalize,
82}
83
84impl Plugin for PreparePlugin {
85 fn build(&self, app: &mut App) {
86 app.configure_sets(
87 self.schedule,
88 (
89 PrepareSet::PreInit,
90 PrepareSet::InitRigidBodies,
91 PrepareSet::InitColliders,
92 PrepareSet::InitMassProperties,
93 PrepareSet::PropagateTransforms,
94 PrepareSet::InitTransforms,
95 PrepareSet::Finalize,
96 )
97 .chain()
98 .in_set(PhysicsSet::Prepare),
99 );
100
101 app.init_resource::<PrepareConfig>()
102 .register_type::<PrepareConfig>();
103
104 app.add_systems(
106 self.schedule,
107 (
109 sync::sync_simple_transforms_physics,
110 sync::propagate_transforms_physics,
111 )
112 .chain()
113 .run_if(match_any::<Added<RigidBody>>)
114 .in_set(PrepareSet::PropagateTransforms),
115 )
116 .add_systems(
117 self.schedule,
118 init_rigid_bodies.in_set(PrepareSet::InitRigidBodies),
119 )
120 .add_systems(
121 self.schedule,
122 init_mass_properties.in_set(PrepareSet::InitMassProperties),
123 )
124 .add_systems(
125 self.schedule,
126 init_transforms::<RigidBody>.in_set(PrepareSet::InitTransforms),
127 )
128 .add_systems(
129 self.schedule,
130 (
131 update_mass_properties,
132 clamp_collider_density,
133 clamp_restitution,
134 apply_deferred,
136 )
137 .chain()
138 .in_set(PrepareSet::Finalize),
139 );
140 }
141}
142
143#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
145#[reflect(Resource)]
146pub struct PrepareConfig {
147 pub position_to_transform: bool,
150 pub transform_to_position: bool,
153}
154
155impl Default for PrepareConfig {
156 fn default() -> Self {
157 PrepareConfig {
158 position_to_transform: true,
159 transform_to_position: true,
160 }
161 }
162}
163
164pub(crate) fn match_any<F: QueryFilter>(query: Query<(), F>) -> bool {
166 !query.is_empty()
167}
168
169pub fn init_transforms<C: Component>(
172 mut commands: Commands,
173 config: Res<PrepareConfig>,
174 query: Query<
175 (
176 Entity,
177 Option<&Transform>,
178 Option<&GlobalTransform>,
179 Option<&Position>,
180 Option<&PreviousPosition>,
181 Option<&Rotation>,
182 Option<&PreviousRotation>,
183 Option<&Parent>,
184 Has<RigidBody>,
185 ),
186 Added<C>,
187 >,
188 parents: Query<
189 (
190 Option<&Position>,
191 Option<&Rotation>,
192 Option<&GlobalTransform>,
193 ),
194 With<Children>,
195 >,
196) {
197 if !config.position_to_transform && !config.transform_to_position {
198 return;
200 }
201
202 for (
203 entity,
204 transform,
205 global_transform,
206 pos,
207 previous_pos,
208 rot,
209 previous_rot,
210 parent,
211 has_rigid_body,
212 ) in &query
213 {
214 let parent_transforms = parent.and_then(|parent| parents.get(parent.get()).ok());
215 let parent_pos = parent_transforms.and_then(|(pos, _, _)| pos);
216 let parent_rot = parent_transforms.and_then(|(_, rot, _)| rot);
217 let parent_global_trans = parent_transforms.and_then(|(_, _, trans)| trans);
218
219 let mut new_transform = if config.position_to_transform {
220 Some(transform.copied().unwrap_or_default())
221 } else {
222 None
223 };
224
225 let new_position = if let Some(pos) = pos {
227 if let Some(transform) = &mut new_transform {
228 #[cfg(feature = "2d")]
230 let mut new_translation = pos.f32().extend(transform.translation.z);
231 #[cfg(feature = "3d")]
232 let mut new_translation = pos.f32();
233
234 if parent.is_some() {
237 if let Some(parent_pos) = parent_pos {
238 #[cfg(feature = "2d")]
239 {
240 new_translation -= parent_pos.f32().extend(new_translation.z);
241 }
242 #[cfg(feature = "3d")]
243 {
244 new_translation -= parent_pos.f32();
245 }
246 } else if let Some(parent_transform) = parent_global_trans {
247 new_translation -= parent_transform.translation();
248 }
249 }
250 transform.translation = new_translation;
251 }
252 pos.0
253 } else if config.transform_to_position {
254 let mut new_position = Vector::ZERO;
255
256 if parent.is_some() {
257 let translation = transform.as_ref().map_or(default(), |t| t.translation);
258 if let Some(parent_pos) = parent_pos {
259 #[cfg(feature = "2d")]
260 {
261 new_position = parent_pos.0 + translation.adjust_precision().truncate();
262 }
263 #[cfg(feature = "3d")]
264 {
265 new_position = parent_pos.0 + translation.adjust_precision();
266 }
267 } else if let Some(parent_transform) = parent_global_trans {
268 let new_pos = parent_transform
269 .transform_point(transform.as_ref().map_or(default(), |t| t.translation));
270 #[cfg(feature = "2d")]
271 {
272 new_position = new_pos.truncate().adjust_precision();
273 }
274 #[cfg(feature = "3d")]
275 {
276 new_position = new_pos.adjust_precision();
277 }
278 }
279 } else {
280 #[cfg(feature = "2d")]
281 {
282 new_position = transform
283 .map(|t| t.translation.truncate().adjust_precision())
284 .unwrap_or(global_transform.as_ref().map_or(Vector::ZERO, |t| {
285 Vector::new(t.translation().x as Scalar, t.translation().y as Scalar)
286 }));
287 }
288 #[cfg(feature = "3d")]
289 {
290 new_position = transform
291 .map(|t| t.translation.adjust_precision())
292 .unwrap_or(
293 global_transform
294 .as_ref()
295 .map_or(Vector::ZERO, |t| t.translation().adjust_precision()),
296 )
297 }
298 };
299
300 new_position
301 } else {
302 default()
303 };
304
305 let new_rotation = if let Some(rot) = rot {
307 if let Some(transform) = &mut new_transform {
308 let mut new_rotation = Quaternion::from(*rot).f32();
310
311 if parent.is_some() {
314 if let Some(parent_rot) = parent_rot {
315 new_rotation *= Quaternion::from(*parent_rot).f32().inverse();
316 } else if let Some(parent_transform) = parent_global_trans {
317 new_rotation *= parent_transform.compute_transform().rotation.inverse();
318 }
319 }
320 transform.rotation = new_rotation;
321 }
322 *rot
323 } else if config.transform_to_position {
324 if parent.is_some() {
325 let parent_rot = parent_rot.copied().unwrap_or(Rotation::from(
326 parent_global_trans.map_or(default(), |t| t.compute_transform().rotation),
327 ));
328 let rot = Rotation::from(transform.as_ref().map_or(default(), |t| t.rotation));
329 #[cfg(feature = "2d")]
330 {
331 parent_rot + rot
332 }
333 #[cfg(feature = "3d")]
334 {
335 Rotation(parent_rot.0 * rot.0)
336 }
337 } else {
338 transform.map(|t| Rotation::from(t.rotation)).unwrap_or(
339 global_transform.map_or(Rotation::default(), |t| {
340 t.compute_transform().rotation.into()
341 }),
342 )
343 }
344 } else {
345 default()
346 };
347
348 let mut cmds = commands.entity(entity);
349
350 match (has_rigid_body, new_transform) {
356 (true, None) => {
357 cmds.try_insert((
358 Position(new_position),
359 new_rotation,
360 *previous_pos.unwrap_or(&PreviousPosition(new_position)),
361 *previous_rot.unwrap_or(&PreviousRotation(new_rotation)),
362 ));
363 }
364 (true, Some(transform)) => {
365 cmds.try_insert((
366 transform,
367 Position(new_position),
368 new_rotation,
369 *previous_pos.unwrap_or(&PreviousPosition(new_position)),
370 *previous_rot.unwrap_or(&PreviousRotation(new_rotation)),
371 ));
372 }
373 (false, None) => {
374 cmds.try_insert((Position(new_position), new_rotation));
375 }
376 (false, Some(transform)) => {
377 cmds.try_insert((transform, Position(new_position), new_rotation));
378 }
379 }
380 }
381}
382
383fn init_rigid_bodies(
385 mut commands: Commands,
386 mut bodies: Query<
387 (
388 Entity,
389 Option<&LinearVelocity>,
390 Option<&AngularVelocity>,
391 Option<&ExternalForce>,
392 Option<&ExternalTorque>,
393 Option<&ExternalImpulse>,
394 Option<&ExternalAngularImpulse>,
395 Option<&Restitution>,
396 Option<&Friction>,
397 Option<&TimeSleeping>,
398 ),
399 Added<RigidBody>,
400 >,
401) {
402 for (
403 entity,
404 lin_vel,
405 ang_vel,
406 force,
407 torque,
408 impulse,
409 angular_impulse,
410 restitution,
411 friction,
412 time_sleeping,
413 ) in &mut bodies
414 {
415 commands.entity(entity).try_insert((
416 AccumulatedTranslation(Vector::ZERO),
417 *lin_vel.unwrap_or(&LinearVelocity::default()),
418 *ang_vel.unwrap_or(&AngularVelocity::default()),
419 PreSolveLinearVelocity::default(),
420 PreSolveAngularVelocity::default(),
421 *force.unwrap_or(&ExternalForce::default()),
422 *torque.unwrap_or(&ExternalTorque::default()),
423 *impulse.unwrap_or(&ExternalImpulse::default()),
424 *angular_impulse.unwrap_or(&ExternalAngularImpulse::default()),
425 *restitution.unwrap_or(&Restitution::default()),
426 *friction.unwrap_or(&Friction::default()),
427 *time_sleeping.unwrap_or(&TimeSleeping::default()),
428 ));
429 }
430}
431
432fn init_mass_properties(
434 mut commands: Commands,
435 mass_properties: Query<
436 (
437 Entity,
438 Option<&Mass>,
439 Option<&InverseMass>,
440 Option<&Inertia>,
441 Option<&InverseInertia>,
442 Option<&CenterOfMass>,
443 ),
444 Added<RigidBody>,
445 >,
446) {
447 for (entity, mass, inverse_mass, inertia, inverse_inertia, center_of_mass) in &mass_properties {
448 commands.entity(entity).try_insert((
449 *mass.unwrap_or(&Mass(
450 inverse_mass.map_or(0.0, |inverse_mass| 1.0 / inverse_mass.0),
451 )),
452 *inverse_mass.unwrap_or(&InverseMass(mass.map_or(0.0, |mass| 1.0 / mass.0))),
453 *inertia.unwrap_or(
454 &inverse_inertia.map_or(Inertia::ZERO, |inverse_inertia| inverse_inertia.inverse()),
455 ),
456 *inverse_inertia
457 .unwrap_or(&inertia.map_or(InverseInertia::ZERO, |inertia| inertia.inverse())),
458 *center_of_mass.unwrap_or(&CenterOfMass::default()),
459 ));
460 }
461}
462
463pub fn update_mass_properties(
465 mut bodies: Query<
466 (
467 Entity,
468 &RigidBody,
469 Ref<Mass>,
470 &mut InverseMass,
471 Ref<Inertia>,
472 &mut InverseInertia,
473 ),
474 Or<(Changed<Mass>, Changed<Inertia>)>,
475 >,
476) {
477 for (entity, rb, mass, mut inv_mass, inertia, mut inv_inertia) in &mut bodies {
478 let is_mass_valid = mass.is_finite() && mass.0 >= Scalar::EPSILON;
479 #[cfg(feature = "2d")]
480 let is_inertia_valid = inertia.is_finite() && inertia.0 >= Scalar::EPSILON;
481 #[cfg(feature = "3d")]
482 let is_inertia_valid = inertia.is_finite() && *inertia != Inertia::ZERO;
483
484 if mass.is_changed() && is_mass_valid {
485 inv_mass.0 = 1.0 / mass.0;
486 }
487 if inertia.is_changed() && is_inertia_valid {
488 inv_inertia.0 = inertia.inverse().0;
489 }
490
491 if rb.is_dynamic() && !(is_mass_valid && is_inertia_valid) {
493 warn!(
494 "Dynamic rigid body {:?} has no mass or inertia. This can cause NaN values. Consider adding a `MassPropertiesBundle` or a `Collider` with mass.",
495 entity
496 );
497 }
498 }
499}
500
501fn clamp_restitution(mut query: Query<&mut Restitution, Changed<Restitution>>) {
503 for mut restitution in &mut query {
504 restitution.coefficient = restitution.coefficient.clamp(0.0, 1.0);
505 }
506}
507
508fn clamp_collider_density(mut query: Query<&mut ColliderDensity, Changed<ColliderDensity>>) {
510 for mut density in &mut query {
511 density.0 = density.max(Scalar::EPSILON);
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518
519 #[test]
520 fn test_init_transforms_basics() {
521 let mut app = App::new();
522
523 app.add_systems(Update, init_transforms::<RigidBody>);
525
526 for (position_to_transform, transform_to_position) in
528 [(true, true), (true, false), (false, true), (false, false)]
529 {
530 let config = PrepareConfig {
531 position_to_transform,
532 transform_to_position,
533 };
534 app.insert_resource(dbg!(config.clone()));
535
536 let (pos_0, rot_0) = {
538 #[cfg(feature = "2d")]
539 {
540 (Position::from_xy(1., 2.), Rotation::from_sin_cos(0.1, 0.2))
541 }
542 #[cfg(feature = "3d")]
543 {
544 (
545 Position::from_xyz(1., 2., 3.),
546 Rotation(Quaternion::from_axis_angle(Vector::Y, 0.5)),
547 )
548 }
549 };
550 let e_0_with_pos_and_rot = app
551 .world_mut()
552 .spawn((RigidBody::Dynamic, pos_0, rot_0))
553 .id();
554
555 let (pos_1, rot_1) = {
556 #[cfg(feature = "2d")]
557 {
558 (Position::from_xy(-1., 3.), Rotation::from_sin_cos(0.2, 0.3))
559 }
560 #[cfg(feature = "3d")]
561 {
562 (
563 Position::from_xyz(-1., 3., -3.),
564 Rotation(Quaternion::from_axis_angle(Vector::X, 0.1)),
565 )
566 }
567 };
568 let e_1_with_pos_and_rot = app
569 .world_mut()
570 .spawn((RigidBody::Dynamic, pos_1, rot_1))
571 .id();
572
573 let pos_2 = {
575 #[cfg(feature = "2d")]
576 {
577 Position::from_xy(10., 1.)
578 }
579 #[cfg(feature = "3d")]
580 {
581 Position::from_xyz(10., 1., 5.)
582 }
583 };
584 let e_2_with_pos = app.world_mut().spawn((RigidBody::Dynamic, pos_2)).id();
585
586 let rot_3 = {
588 #[cfg(feature = "2d")]
589 {
590 Rotation::from_sin_cos(0.4, 0.5)
591 }
592 #[cfg(feature = "3d")]
593 {
594 Rotation(Quaternion::from_axis_angle(Vector::Z, 0.4))
595 }
596 };
597 let e_3_with_rot = app.world_mut().spawn((RigidBody::Dynamic, rot_3)).id();
598
599 let trans_4 = {
601 Transform {
602 translation: Vec3::new(-1.1, 6., -7.),
603 rotation: Quat::from_axis_angle(Vec3::Y, 0.1),
604 scale: Vec3::ONE,
605 }
606 };
607 let e_4_with_trans = app.world_mut().spawn((RigidBody::Dynamic, trans_4)).id();
608
609 let trans_5 = {
610 Transform {
611 translation: Vec3::new(8., -1., 0.),
612 rotation: Quat::from_axis_angle(Vec3::Y, -0.1),
613 scale: Vec3::ONE,
614 }
615 };
616 let e_5_with_trans = app.world_mut().spawn((RigidBody::Dynamic, trans_5)).id();
617
618 let e_6_without_trans = app.world_mut().spawn(RigidBody::Dynamic).id();
620
621 let e_7_without_rb = app.world_mut().spawn(()).id();
623
624 app.update();
626
627 if config.position_to_transform {
629 assert!(app.world().get::<Transform>(e_0_with_pos_and_rot).is_some());
630 let transform = app.world().get::<Transform>(e_0_with_pos_and_rot).unwrap();
631 let expected: Vec3 = {
632 #[cfg(feature = "2d")]
633 {
634 pos_0.f32().extend(0.)
635 }
636 #[cfg(feature = "3d")]
637 {
638 pos_0.f32()
639 }
640 };
641 assert_eq!(transform.translation, expected);
642 let expected = Quaternion::from(rot_0).f32();
643 assert_eq!(transform.rotation, expected);
644
645 assert!(app.world().get::<Transform>(e_1_with_pos_and_rot).is_some());
646 let transform = app.world().get::<Transform>(e_1_with_pos_and_rot).unwrap();
647 let expected: Vec3 = {
648 #[cfg(feature = "2d")]
649 {
650 pos_1.f32().extend(0.)
651 }
652 #[cfg(feature = "3d")]
653 {
654 pos_1.f32()
655 }
656 };
657 assert_eq!(transform.translation, expected);
658 let expected = Quaternion::from(rot_1).f32();
659 assert_eq!(transform.rotation, expected);
660
661 assert!(app.world().get::<Transform>(e_2_with_pos).is_some());
662 let transform = app.world().get::<Transform>(e_2_with_pos).unwrap();
663 let expected: Vec3 = {
664 #[cfg(feature = "2d")]
665 {
666 pos_2.f32().extend(0.)
667 }
668 #[cfg(feature = "3d")]
669 {
670 pos_2.f32()
671 }
672 };
673 assert_eq!(transform.translation, expected);
674 let expected = Quat::default();
675 assert_eq!(transform.rotation, expected);
676
677 assert!(app.world().get::<Transform>(e_3_with_rot).is_some());
678 let transform = app.world().get::<Transform>(e_3_with_rot).unwrap();
679 let expected: Vec3 = Vec3::default();
680 assert_eq!(transform.translation, expected);
681 let expected = Quaternion::from(rot_3).f32();
682 assert_eq!(transform.rotation, expected);
683
684 assert!(app.world().get::<Transform>(e_4_with_trans).is_some());
685 let transform = app.world().get::<Transform>(e_4_with_trans).unwrap();
686 assert_eq!(transform, &trans_4);
687
688 assert!(app.world().get::<Transform>(e_5_with_trans).is_some());
689 let transform = app.world().get::<Transform>(e_5_with_trans).unwrap();
690 assert_eq!(transform, &trans_5);
691
692 assert!(app.world().get::<Transform>(e_6_without_trans).is_some());
693 let transform = app.world().get::<Transform>(e_6_without_trans).unwrap();
694 assert_eq!(transform, &Transform::default());
695
696 assert!(app.world().get::<Transform>(e_7_without_rb).is_none());
697 }
698
699 if config.transform_to_position {
700 assert!(app.world().get::<Position>(e_0_with_pos_and_rot).is_some());
701 let pos = app.world().get::<Position>(e_0_with_pos_and_rot).unwrap();
702 assert_eq!(pos, &pos_0);
703 assert!(app.world().get::<Rotation>(e_0_with_pos_and_rot).is_some());
704 let rot = app.world().get::<Rotation>(e_0_with_pos_and_rot).unwrap();
705 assert_eq!(rot, &rot_0);
706
707 assert!(app.world().get::<Position>(e_1_with_pos_and_rot).is_some());
708 let pos = app.world().get::<Position>(e_1_with_pos_and_rot).unwrap();
709 assert_eq!(pos, &pos_1);
710 assert!(app.world().get::<Rotation>(e_1_with_pos_and_rot).is_some());
711 let rot = app.world().get::<Rotation>(e_1_with_pos_and_rot).unwrap();
712 assert_eq!(rot, &rot_1);
713
714 assert!(app.world().get::<Position>(e_2_with_pos).is_some());
715 let pos = app.world().get::<Position>(e_2_with_pos).unwrap();
716 assert_eq!(pos, &pos_2);
717 assert!(app.world().get::<Rotation>(e_2_with_pos).is_some());
718 let rot = app.world().get::<Rotation>(e_2_with_pos).unwrap();
719 assert_eq!(rot, &Rotation::default());
720
721 assert!(app.world().get::<Position>(e_3_with_rot).is_some());
722 let pos = app.world().get::<Position>(e_3_with_rot).unwrap();
723 assert_eq!(pos, &Position::default());
724 assert!(app.world().get::<Rotation>(e_3_with_rot).is_some());
725 let rot = app.world().get::<Rotation>(e_3_with_rot).unwrap();
726 assert_eq!(rot, &rot_3);
727
728 assert!(app.world().get::<Position>(e_4_with_trans).is_some());
729 let pos = app.world().get::<Position>(e_4_with_trans).unwrap();
730 let expected: Position = Position::new({
731 #[cfg(feature = "2d")]
732 {
733 trans_4.translation.truncate().adjust_precision()
734 }
735 #[cfg(feature = "3d")]
736 {
737 trans_4.translation.adjust_precision()
738 }
739 });
740 assert_eq!(pos, &expected);
741 assert!(app.world().get::<Rotation>(e_4_with_trans).is_some());
742 let rot = app.world().get::<Rotation>(e_4_with_trans).unwrap();
743 assert_eq!(rot, &Rotation::from(trans_4.rotation));
744
745 assert!(app.world().get::<Position>(e_5_with_trans).is_some());
746 let pos = app.world().get::<Position>(e_5_with_trans).unwrap();
747 let expected: Position = Position::new({
748 #[cfg(feature = "2d")]
749 {
750 trans_5.translation.truncate().adjust_precision()
751 }
752 #[cfg(feature = "3d")]
753 {
754 trans_5.translation.adjust_precision()
755 }
756 });
757 assert_eq!(pos, &expected);
758 assert!(app.world().get::<Rotation>(e_5_with_trans).is_some());
759 let rot = app.world().get::<Rotation>(e_5_with_trans).unwrap();
760 assert_eq!(rot, &Rotation::from(trans_5.rotation));
761
762 assert!(app.world().get::<Position>(e_6_without_trans).is_some());
763 let pos = app.world().get::<Position>(e_6_without_trans).unwrap();
764 assert_eq!(pos, &Position::default());
765 assert!(app.world().get::<Rotation>(e_6_without_trans).is_some());
766 let rot = app.world().get::<Rotation>(e_6_without_trans).unwrap();
767 assert_eq!(rot, &Rotation::default());
768
769 assert!(app.world().get::<Position>(e_7_without_rb).is_none());
770 assert!(app.world().get::<Rotation>(e_7_without_rb).is_none());
771 }
772 }
773 }
774}