avian3d/
prepare.rs

1//! Runs systems that prepare and initialize components used by physics.
2//!
3//! See [`PreparePlugin`].
4
5#![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
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/// - Clamps restitution coefficients between 0 and 1
19///
20/// The [`Transform`] component will be initialized based on [`Position`] or [`Rotation`]
21/// and vice versa. You can configure this synchronization using the [`PrepareConfig`] resource.
22///
23/// The plugin takes a collider type. This should be [`Collider`] for
24/// the vast majority of applications, but for custom collisión backends
25/// you may use any collider that implements the [`AnyCollider`] trait.
26///
27/// The systems run in [`PhysicsSet::Prepare`].
28pub struct PreparePlugin {
29    schedule: Interned<dyn ScheduleLabel>,
30}
31
32impl PreparePlugin {
33    /// Creates a [`PreparePlugin`] with the schedule that is used for running the [`PhysicsSchedule`].
34    ///
35    /// The default schedule is `FixedPostUpdate`.
36    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/// Systems sets for initializing and syncing missing components.
50/// You can use these to schedule your own initialization systems
51/// without having to worry about implementation details.
52///
53/// 1. `First`: Runs at the start of the preparation step.
54/// 2. `PropagateTransforms`: Responsible for propagating transforms.
55/// 3. `InitTransforms`: Responsible for initializing [`Transform`] based on [`Position`] and [`Rotation`]
56///    or vice versa.
57/// 4. `Finalize`: Responsible for performing final updates after everything is initialized and updated.
58#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub enum PrepareSet {
60    /// Runs at the start of the preparation step.
61    First,
62    /// Responsible for propagating transforms.
63    PropagateTransforms,
64    /// Responsible for initializing [`Transform`] based on [`Position`] and [`Rotation`]
65    /// or vice versa. Parts of this system can be disabled with [`PrepareConfig`].
66    /// Schedule your system with this to implement custom behavior for initializing transforms.
67    InitTransforms,
68    /// Responsible for performing final updates after everything is initialized.
69    /// Updates mass properties and clamps collider density and restitution.
70    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        // Note: Collider logic is handled by the `ColliderBackendPlugin`
93        app.add_systems(
94            self.schedule,
95            // Run transform propagation if new bodies have been added
96            (
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/// Configures what is initialized by the [`PreparePlugin`] and how.
113#[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)]
114#[reflect(Resource)]
115pub struct PrepareConfig {
116    /// Initializes [`Transform`] based on [`Position`] and [`Rotation`].
117    /// Defaults to true.
118    pub position_to_transform: bool,
119    /// Initializes [`Position`] and [`Rotation`] based on [`Transform`].
120    /// Defaults to true.
121    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
133/// A run condition that returns `true` if any entity matches the given query filter.
134pub(crate) fn match_any<F: QueryFilter>(query: Query<(), F>) -> bool {
135    !query.is_empty()
136}
137
138/// Initializes [`Transform`] based on [`Position`] and [`Rotation`] or vice versa
139/// when a component of the given type is inserted.
140pub 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        // Nothing to do
167        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        // Compute Transform based on Position or vice versa
183        let new_position = if let Some(pos) = pos {
184            if let Some(transform) = &mut new_transform {
185                // Initialize new translation as global position
186                #[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 the body is a child, subtract the parent's global translation
192                // to get the local translation
193                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        // Compute Transform based on Rotation or vice versa
263        let new_rotation = if let Some(rot) = rot {
264            if let Some(transform) = &mut new_transform {
265                // Initialize new rotation as global rotation
266                let mut new_rotation = Quaternion::from(*rot).f32();
267
268                // If the body is a child, subtract the parent's global rotation
269                // to get the local rotation
270                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        // Insert the position and rotation.
308        // The values are either unchanged (Position and Rotation already exist)
309        // or computed based on the GlobalTransform.
310        // If the entity isn't a rigid body, adding PreSolveAccumulatedTranslation and PreviousRotation
311        // is unnecessary.
312        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        // Add system under test
351        app.add_systems(Update, init_transforms::<RigidBody>);
352
353        // Test all possible config permutations
354        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            // Spawn entities with `Position` and `Rotation`
364            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            // Spawn an entity with only `Position`
401            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            // Spawn an entity with only `Rotation`
414            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            // Spawn entities with `Transform`
427            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            // Spawn entity without any transforms
446            let e_6_without_trans = app.world_mut().spawn(RigidBody::Dynamic).id();
447
448            // Spawn entity without a ridid body
449            let e_7_without_rb = app.world_mut().spawn(()).id();
450
451            // Run the system
452            app.update();
453
454            // Check the results are as expected
455            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}