bevy_xpbd_3d/plugins/
prepare.rs

1//! Runs systems at the start of each physics frame. Initializes [rigid bodies](RigidBody)
2//! and updates components.
3//!
4//! See [`PreparePlugin`].
5
6#![allow(clippy::type_complexity)]
7
8use crate::prelude::*;
9use bevy::{
10    ecs::{intern::Interned, query::QueryFilter},
11    prelude::*,
12};
13
14/// Runs systems at the start of each physics frame. Initializes [rigid bodies](RigidBody)
15/// and updates components.
16///
17/// - Adds missing rigid body components for entities with a [`RigidBody`] component
18/// - Adds missing mass properties for entities with a [`RigidBody`] component
19/// - Updates mass properties
20/// - Clamps restitution coefficients between 0 and 1
21///
22/// The [`Transform`] component will be initialized based on [`Position`] or [`Rotation`]
23/// and vice versa. You can configure this synchronization using the [`PrepareConfig`] resource.
24///
25/// The plugin takes a collider type. This should be [`Collider`] for
26/// the vast majority of applications, but for custom collisión backends
27/// you may use any collider that implements the [`AnyCollider`] trait.
28///
29/// The systems run in [`PhysicsSet::Prepare`].
30pub struct PreparePlugin {
31    schedule: Interned<dyn ScheduleLabel>,
32}
33
34impl PreparePlugin {
35    /// Creates a [`PreparePlugin`] with the schedule that is used for running the [`PhysicsSchedule`].
36    ///
37    /// The default schedule is `PostUpdate`.
38    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/// Systems sets for initializing and syncing missing components.
52/// You can use these to schedule your own initialization systems
53/// without having to worry about implementation details.
54///
55/// 1. `PreInit`: Used for systems that must run before initialization.
56/// 2. `InitRigidBodies`: Responsible for initializing missing [`RigidBody`] components.
57/// 3. `InitColliders`: Responsible for initializing missing [`Collider`] components.
58/// 4. `PropagateTransforms`: Responsible for propagating transforms.
59/// 5. `InitMassProperties`: Responsible for initializing missing mass properties for [`RigidBody`] components.
60/// 6. `InitTransforms`: Responsible for initializing [`Transform`] based on [`Position`] and [`Rotation`]
61/// or vice versa.
62/// 7. `Finalize`: Responsible for performing final updates after everything is initialized.
63#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub enum PrepareSet {
65    /// Used for systems that must run before initialization.
66    PreInit,
67    /// Responsible for initializing missing [`RigidBody`] components.
68    InitRigidBodies,
69    /// Responsible for initializing missing [`Collider`] components.
70    InitColliders,
71    /// Responsible for propagating transforms.
72    PropagateTransforms,
73    /// Responsible for initializing missing mass properties for [`RigidBody`] components.
74    InitMassProperties,
75    /// Responsible for initializing [`Transform`] based on [`Position`] and [`Rotation`]
76    /// or vice versa. Parts of this system can be disabled with [`PrepareConfig`].
77    /// Schedule your system with this to implement custom behavior for initializing transforms.
78    InitTransforms,
79    /// Responsible for performing final updates after everything is initialized.
80    /// Updates mass properties and clamps collider density and restitution.
81    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        // Note: Collider logic is handled by the `ColliderBackendPlugin`
105        app.add_systems(
106            self.schedule,
107            // Run transform propagation if new bodies have been added
108            (
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                // All the components we added above must exist before we can simulate the bodies.
135                apply_deferred,
136            )
137                .chain()
138                .in_set(PrepareSet::Finalize),
139        );
140    }
141}
142
143/// Configures what is initialized by the [`PreparePlugin`] and how.
144#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
145#[reflect(Resource)]
146pub struct PrepareConfig {
147    /// Initializes [`Transform`] based on [`Position`] and [`Rotation`].
148    /// Defaults to true.
149    pub position_to_transform: bool,
150    /// Initializes [`Position`] and [`Rotation`] based on [`Transform`].
151    /// Defaults to true.
152    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
164/// A run condition that returns `true` if any entity matches the given query filter.
165pub(crate) fn match_any<F: QueryFilter>(query: Query<(), F>) -> bool {
166    !query.is_empty()
167}
168
169/// Initializes [`Transform`] based on [`Position`] and [`Rotation`] or vice versa
170/// when a component of the given type is inserted.
171pub 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        // Nothing to do
199        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        // Compute Transform based on Position or vice versa
226        let new_position = if let Some(pos) = pos {
227            if let Some(transform) = &mut new_transform {
228                // Initialize new translation as global position
229                #[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 the body is a child, subtract the parent's global translation
235                // to get the local translation
236                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        // Compute Transform based on Rotation or vice versa
306        let new_rotation = if let Some(rot) = rot {
307            if let Some(transform) = &mut new_transform {
308                // Initialize new rotation as global rotation
309                let mut new_rotation = Quaternion::from(*rot).f32();
310
311                // If the body is a child, subtract the parent's global rotation
312                // to get the local rotation
313                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        // Insert the position and rotation.
351        // The values are either unchanged (Position and Rotation already exist)
352        // or computed based on the GlobalTransform.
353        // If the entity isn't a rigid body, adding PreviousPosition and PreviousRotation
354        // is unnecessary.
355        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
383/// Initializes missing components for [rigid bodies](RigidBody).
384fn 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
432/// Initializes missing mass properties for [rigid bodies](RigidBody).
433fn 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
463/// Updates each body's [`InverseMass`] and [`InverseInertia`] whenever [`Mass`] or [`Inertia`] are changed.
464pub 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        // Warn about dynamic bodies with no mass or inertia
492        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
501/// Clamps coefficients of [restitution](Restitution) to be between 0.0 and 1.0.
502fn 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
508/// Clamps [`ColliderDensity`] to be above 0.0.
509fn 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        // Add system under test
524        app.add_systems(Update, init_transforms::<RigidBody>);
525
526        // Test all possible config permutations
527        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            // Spawn entities with `Position` and `Rotation`
537            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            // Spawn an entity with only `Position`
574            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            // Spawn an entity with only `Rotation`
587            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            // Spawn entities with `Transform`
600            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            // Spawn entity without any transforms
619            let e_6_without_trans = app.world_mut().spawn(RigidBody::Dynamic).id();
620
621            // Spawn entity without a ridid body
622            let e_7_without_rb = app.world_mut().spawn(()).id();
623
624            // Run the system
625            app.update();
626
627            // Check the results are as expected
628            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}