bevy_rapier3d/plugin/
plugin.rs

1use crate::pipeline::{CollisionEvent, ContactForceEvent};
2use crate::prelude::*;
3use crate::reflect::{IntegrationParametersWrapper, SpringCoefficientsWrapper};
4use bevy::app::DynEq;
5use bevy::ecs::{
6    intern::Interned,
7    schedule::{IntoScheduleConfigs, ScheduleConfigs, ScheduleLabel},
8    system::{ScheduleSystem, SystemParamItem},
9};
10use bevy::platform::collections::HashSet;
11use bevy::{prelude::*, transform::TransformSystems};
12use rapier::dynamics::IntegrationParameters;
13use std::marker::PhantomData;
14
15use super::context::DefaultRapierContext;
16
17#[cfg(doc)]
18use crate::plugin::context::systemparams::RapierContext;
19
20/// No specific user-data is associated to the hooks.
21pub type NoUserData = ();
22
23/// A plugin responsible for setting up a full Rapier physics simulation pipeline and resources.
24///
25/// This will automatically setup all the resources needed to run a physics simulation with the
26/// Rapier physics engine.
27pub struct RapierPhysicsPlugin<PhysicsHooks = ()> {
28    schedule: Interned<dyn ScheduleLabel>,
29    default_system_setup: bool,
30    /// Read during [`RapierPhysicsPlugin::build()`],
31    /// to help initializing [`RapierContextInitialization`] resource.
32    /// This will be ignored if that resource already exists.
33    default_world_setup: RapierContextInitialization,
34    /// Controls whether given `PhysicsSets` systems are injected into the scheduler.
35    ///
36    /// This is useful to opt out of default plugin behaviour, for example if you need to reorganize
37    /// the systems in different schedules.
38    ///
39    /// If passing an empty set, the plugin will still add the Physics Sets to the plugin schedule,
40    /// but no systems will be added automatically.
41    enabled_physics_schedules: HashSet<PhysicsSet>,
42    _phantom: PhantomData<PhysicsHooks>,
43}
44
45impl<PhysicsHooks> RapierPhysicsPlugin<PhysicsHooks>
46where
47    PhysicsHooks: 'static + BevyPhysicsHooks,
48    for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
49{
50    /// Specifies a scale ratio between the physics world and the bevy transforms.
51    ///
52    /// This affects the size of every elements in the physics engine, by multiplying
53    /// all the length-related quantities by the `length_unit` factor. This should
54    /// likely always be 1.0 in 3D. In 2D, this is useful to specify a "pixels-per-meter"
55    /// conversion ratio.
56    pub fn with_length_unit(mut self, length_unit: f32) -> Self {
57        self.default_world_setup =
58            RapierContextInitialization::default_with_length_unit(length_unit);
59        self
60    }
61
62    /// Specifies a default world initialization strategy.
63    ///
64    /// The default is to initialize a [`RapierContext`] with a length unit of 1.
65    pub fn with_custom_initialization(
66        mut self,
67        default_world_initialization: RapierContextInitialization,
68    ) -> Self {
69        self.default_world_setup = default_world_initialization;
70        self
71    }
72
73    /// Specifies whether the plugin should setup each of its [`PhysicsSet`]
74    /// (`true`), or if the user will set them up later (`false`).
75    ///
76    /// The default value is `true`.
77    pub fn with_default_system_setup(mut self, default_system_setup: bool) -> Self {
78        self.default_system_setup = default_system_setup;
79        self
80    }
81
82    /// Specifies how many pixels on the 2D canvas equal one meter on the physics world.
83    ///
84    /// This conversion unit assumes that the 2D camera uses an unscaled projection.
85    #[cfg(feature = "dim2")]
86    pub fn pixels_per_meter(pixels_per_meter: f32) -> Self {
87        Self {
88            default_system_setup: true,
89            default_world_setup: RapierContextInitialization::default_with_length_unit(
90                pixels_per_meter,
91            ),
92            ..default()
93        }
94    }
95
96    /// Controls whether given `PhysicsSets` systems are injected into the scheduler.
97    ///
98    /// This is useful to opt out of default plugin behaviour, for example if you need to reorganize
99    /// the systems in different schedules.
100    ///
101    /// If passing an empty set, the plugin will still add the Physics Sets to the plugin schedule,
102    /// but no systems will be added automatically.
103    pub fn with_physics_sets_systems(
104        mut self,
105        enabled_physics_schedules: HashSet<PhysicsSet>,
106    ) -> Self {
107        self.enabled_physics_schedules = enabled_physics_schedules;
108        self
109    }
110
111    /// Adds the physics systems to the `FixedUpdate` schedule rather than `PostUpdate`.
112    pub fn in_fixed_schedule(self) -> Self {
113        self.in_schedule(FixedUpdate)
114    }
115
116    /// Adds the physics systems to the provided schedule rather than `PostUpdate`.
117    pub fn in_schedule(mut self, schedule: impl ScheduleLabel) -> Self {
118        self.schedule = schedule.intern();
119        self
120    }
121
122    /// Provided for use when staging systems outside of this plugin using
123    /// [`with_default_system_setup(false)`](Self::with_default_system_setup).
124    /// See [`PhysicsSet`] for a description of these systems.
125    pub fn get_systems(set: PhysicsSet) -> ScheduleConfigs<ScheduleSystem> {
126        match set {
127            PhysicsSet::SyncBackend => (
128                (
129                    // Initialize the rapier configuration.
130                    // A good candidate for required component or hook components.
131                    // The configuration is needed for following systems, so it should be chained.
132                    setup_rapier_configuration,
133                    // Run the character controller before the manual transform propagation.
134                    systems::update_character_controls,
135                )
136                    .chain()
137                    .in_set(PhysicsSet::SyncBackend),
138                // Run Bevy transform propagation additionally to sync [`GlobalTransform`]
139                (
140                    bevy::transform::systems::sync_simple_transforms,
141                    bevy::transform::systems::propagate_parent_transforms,
142                )
143                    .chain()
144                    .in_set(RapierTransformPropagateSet),
145                (
146                    (
147                        systems::on_add_entity_with_parent,
148                        systems::on_change_context,
149                        systems::sync_removals,
150                        #[cfg(all(feature = "dim3", feature = "async-collider"))]
151                        systems::init_async_scene_colliders,
152                        #[cfg(all(feature = "dim3", feature = "async-collider"))]
153                        systems::init_async_colliders,
154                        systems::init_rigid_bodies,
155                        systems::init_colliders,
156                        systems::init_joints,
157                    )
158                        .chain()
159                        .in_set(PhysicsSet::SyncBackend),
160                    (
161                        (
162                            systems::apply_scale.in_set(RapierBevyComponentApply),
163                            systems::apply_collider_user_changes.in_set(RapierBevyComponentApply),
164                        )
165                            .chain(),
166                        systems::apply_joint_user_changes.in_set(RapierBevyComponentApply),
167                    ),
168                    // TODO: joints and colliders might be parallelizable.
169                    systems::apply_initial_rigid_body_impulses.in_set(RapierBevyComponentApply),
170                    systems::apply_rigid_body_user_changes.in_set(RapierBevyComponentApply),
171                )
172                    .chain(),
173            )
174                .chain()
175                .into_configs(),
176            PhysicsSet::StepSimulation => (systems::step_simulation::<PhysicsHooks>)
177                .in_set(PhysicsSet::StepSimulation)
178                .into_configs(),
179            PhysicsSet::Writeback => (
180                systems::update_colliding_entities,
181                systems::writeback_rigid_bodies,
182                // Each writeback write to different properties.
183                systems::writeback_mass_properties.ambiguous_with(systems::writeback_rigid_bodies),
184            )
185                .in_set(PhysicsSet::Writeback)
186                .into_configs(),
187        }
188    }
189}
190
191/// A set for rapier's copying bevy_rapier's Bevy components back into rapier.
192#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
193pub struct RapierBevyComponentApply;
194
195/// A set for rapier's copy of Bevy's transform propagation systems.
196///
197/// See [`TransformSystems`](bevy::transform::TransformSystems::Propagate).
198#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
199pub struct RapierTransformPropagateSet;
200
201impl<PhysicsHooksSystemParam> Default for RapierPhysicsPlugin<PhysicsHooksSystemParam> {
202    fn default() -> Self {
203        Self {
204            schedule: PostUpdate.intern(),
205            default_system_setup: true,
206            default_world_setup: Default::default(),
207            enabled_physics_schedules: HashSet::from_iter([
208                PhysicsSet::SyncBackend,
209                PhysicsSet::StepSimulation,
210                PhysicsSet::Writeback,
211            ]),
212            _phantom: PhantomData,
213        }
214    }
215}
216
217/// [`SystemSet`] for each phase of the plugin.
218#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
219pub enum PhysicsSet {
220    /// This set runs the systems responsible for synchronizing (and
221    /// initializing) backend data structures with current component state.
222    /// These systems typically run at the after [`Update`].
223    SyncBackend,
224    /// The systems responsible for advancing the physics simulation, and
225    /// updating the internal state for scene queries.
226    /// These systems typically run immediately after [`PhysicsSet::SyncBackend`].
227    StepSimulation,
228    /// The systems responsible for updating
229    /// [`crate::geometry::CollidingEntities`] and writing
230    /// the result of the last simulation step into our `bevy_rapier`
231    /// components and the [`GlobalTransform`] component.
232    /// These systems typically run immediately after [`PhysicsSet::StepSimulation`].
233    Writeback,
234}
235
236impl<PhysicsHooks> Plugin for RapierPhysicsPlugin<PhysicsHooks>
237where
238    PhysicsHooks: 'static + BevyPhysicsHooks,
239    for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
240{
241    fn build(&self, app: &mut App) {
242        // Register components as reflectable.
243        app.register_type::<RigidBody>()
244            .register_type::<Velocity>()
245            .register_type::<AdditionalMassProperties>()
246            .register_type::<MassProperties>()
247            .register_type::<LockedAxes>()
248            .register_type::<ExternalForce>()
249            .register_type::<ExternalImpulse>()
250            .register_type::<Sleeping>()
251            .register_type::<Damping>()
252            .register_type::<Dominance>()
253            .register_type::<Ccd>()
254            .register_type::<SoftCcd>()
255            .register_type::<GravityScale>()
256            .register_type::<CollidingEntities>()
257            .register_type::<Sensor>()
258            .register_type::<Friction>()
259            .register_type::<Restitution>()
260            .register_type::<CollisionGroups>()
261            .register_type::<SolverGroups>()
262            .register_type::<ContactForceEventThreshold>()
263            .register_type::<ContactSkin>()
264            .register_type::<Group>()
265            .register_type::<RapierContextEntityLink>()
266            .register_type::<RapierConfiguration>()
267            .register_type::<SimulationToRenderTime>()
268            .register_type::<DefaultRapierContext>()
269            .register_type::<RapierContextInitialization>()
270            .register_type::<SpringCoefficientsWrapper>();
271
272        app.insert_resource(Messages::<CollisionEvent>::default())
273            .insert_resource(Messages::<ContactForceEvent>::default())
274            .insert_resource(Messages::<MassModifiedEvent>::default());
275        let default_world_init = app.world().get_resource::<RapierContextInitialization>();
276        if let Some(world_init) = default_world_init {
277            log::warn!("RapierPhysicsPlugin added but a `RapierContextInitialization` resource was already existing.\
278            This might overwrite previous configuration made via `RapierPhysicsPlugin::with_custom_initialization`\
279            or `RapierPhysicsPlugin::with_length_unit`.
280            The following resource will be used: {world_init:?}");
281        } else {
282            app.insert_resource(self.default_world_setup.clone());
283        }
284
285        app.add_systems(
286            PreStartup,
287            (insert_default_context, setup_rapier_configuration).chain(),
288        );
289
290        // These *must* be in the main schedule currently so that they do not miss events.
291        // See test `test_sync_removal` for an example of this.
292        if self.schedule != PostUpdate.intern() {
293            app.add_systems(
294                PostUpdate,
295                (systems::sync_removals,).before(TransformSystems::Propagate),
296            );
297        }
298
299        // Add each set as necessary
300        if self.default_system_setup {
301            app.configure_sets(
302                self.schedule,
303                (
304                    PhysicsSet::SyncBackend,
305                    PhysicsSet::StepSimulation,
306                    PhysicsSet::Writeback,
307                )
308                    .chain()
309                    .before(TransformSystems::Propagate),
310            );
311            app.configure_sets(
312                self.schedule,
313                RapierTransformPropagateSet.in_set(PhysicsSet::SyncBackend),
314            );
315            app.configure_sets(
316                self.schedule,
317                RapierBevyComponentApply.in_set(PhysicsSet::SyncBackend),
318            );
319            let mut add_systems_if_enabled = |physics_set: PhysicsSet| {
320                if self.enabled_physics_schedules.contains(&physics_set) {
321                    app.add_systems(self.schedule, Self::get_systems(physics_set));
322                }
323            };
324            add_systems_if_enabled(PhysicsSet::SyncBackend);
325            add_systems_if_enabled(PhysicsSet::Writeback);
326            add_systems_if_enabled(PhysicsSet::StepSimulation);
327
328            app.init_resource::<TimestepMode>();
329
330            // Warn user if the timestep mode isn't in Fixed
331            if self.schedule.dyn_eq(&FixedUpdate as &dyn DynEq) {
332                let config = app.world_mut().resource::<TimestepMode>();
333                match config {
334                    TimestepMode::Fixed { .. } => {}
335                    mode => {
336                        log::warn!("TimestepMode is set to `{mode:?}`, it is recommended to use `TimestepMode::Fixed` if you have the physics in `FixedUpdate`");
337                    }
338                }
339            }
340        }
341    }
342
343    fn finish(&self, _app: &mut App) {
344        #[cfg(all(feature = "dim3", feature = "async-collider"))]
345        {
346            use bevy::{asset::AssetPlugin, mesh::MeshPlugin, scene::ScenePlugin};
347            if !_app.is_plugin_added::<AssetPlugin>() {
348                _app.add_plugins(AssetPlugin::default());
349            }
350            if !_app.is_plugin_added::<MeshPlugin>() {
351                _app.add_plugins(MeshPlugin);
352            }
353            if !_app.is_plugin_added::<ScenePlugin>() {
354                _app.add_plugins(ScenePlugin);
355            }
356        }
357    }
358}
359
360/// Specifies a default configuration for the default [`RapierContext`]
361///
362/// Designed to be passed as parameter to [`RapierPhysicsPlugin::with_custom_initialization`].
363#[derive(Resource, Debug, Reflect, Clone)]
364pub enum RapierContextInitialization {
365    /// [`RapierPhysicsPlugin`] will not spawn any entity containing [`RapierContextSimulation`] automatically.
366    ///
367    /// You are responsible for creating a [`RapierContextSimulation`],
368    /// before spawning any rapier entities (rigidbodies, colliders, joints).
369    ///
370    /// You might be interested in adding [`DefaultRapierContext`] to the created physics context.
371    NoAutomaticRapierContext,
372    /// [`RapierPhysicsPlugin`] will spawn an entity containing a [`RapierContextSimulation`]
373    /// automatically during [`PreStartup`], with the [`DefaultRapierContext`] marker component.
374    InitializeDefaultRapierContext {
375        /// Integration parameters component which will be added to the default rapier context.
376        #[reflect(remote = IntegrationParametersWrapper)]
377        integration_parameters: IntegrationParameters,
378        /// Rapier configuration component which will be added to the default rapier context.
379        rapier_configuration: RapierConfiguration,
380    },
381}
382
383impl Default for RapierContextInitialization {
384    fn default() -> Self {
385        Self::default_with_length_unit(1f32)
386    }
387}
388
389impl RapierContextInitialization {
390    /// Configures rapier with the specified length unit.
391    ///
392    /// See the documentation of [`IntegrationParameters::length_unit`] for additional details
393    /// on that argument.
394    ///
395    /// The default gravity is automatically scaled by that length unit.
396    pub fn default_with_length_unit(length_unit: f32) -> Self {
397        let integration_parameters = IntegrationParameters {
398            length_unit,
399            ..default()
400        };
401
402        RapierContextInitialization::InitializeDefaultRapierContext {
403            integration_parameters,
404            rapier_configuration: RapierConfiguration::new(length_unit),
405        }
406    }
407}
408
409pub fn insert_default_context(
410    mut commands: Commands,
411    initialization_data: Res<RapierContextInitialization>,
412) {
413    match initialization_data.as_ref() {
414        RapierContextInitialization::NoAutomaticRapierContext => {}
415        RapierContextInitialization::InitializeDefaultRapierContext {
416            integration_parameters,
417            rapier_configuration,
418        } => {
419            commands.spawn((
420                Name::new("Rapier Context"),
421                RapierContextSimulation {
422                    integration_parameters: *integration_parameters,
423                    ..RapierContextSimulation::default()
424                },
425                *rapier_configuration,
426                DefaultRapierContext,
427            ));
428        }
429    }
430}
431
432pub fn setup_rapier_configuration(
433    mut commands: Commands,
434    rapier_context: Query<(Entity, &RapierContextSimulation), Without<RapierConfiguration>>,
435) {
436    for (e, rapier_context) in rapier_context.iter() {
437        commands.entity(e).insert(RapierConfiguration::new(
438            rapier_context.integration_parameters.length_unit,
439        ));
440    }
441}
442
443#[cfg(test)]
444mod test {
445
446    use bevy::{
447        ecs::schedule::{NodeId, Stepping},
448        prelude::Component,
449        time::{TimePlugin, TimeUpdateStrategy},
450    };
451    use rapier::{data::Index, dynamics::RigidBodyHandle};
452
453    use crate::{plugin::context::*, plugin::*, prelude::*};
454
455    #[cfg(feature = "dim3")]
456    fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
457        Collider::cuboid(hx, hy, hz)
458    }
459    #[cfg(feature = "dim2")]
460    fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
461        Collider::cuboid(hx, hy)
462    }
463
464    #[derive(Component)]
465    pub struct TestMarker;
466
467    #[test]
468    pub fn hierarchy_link_propagation() {
469        return main();
470
471        use bevy::prelude::*;
472
473        fn run_test(app: &mut App) {
474            app.insert_resource(TimeUpdateStrategy::ManualDuration(
475                std::time::Duration::from_secs_f32(1f32 / 60f32),
476            ));
477
478            app.add_systems(Update, setup_physics);
479
480            app.finish();
481
482            let mut stepping = Stepping::new();
483
484            app.update();
485
486            stepping
487                .add_schedule(PostUpdate)
488                .add_schedule(Update)
489                .enable()
490                .set_breakpoint(PostUpdate, systems::on_add_entity_with_parent)
491                .set_breakpoint(PostUpdate, systems::init_rigid_bodies)
492                .set_breakpoint(PostUpdate, systems::on_change_context)
493                .set_breakpoint(PostUpdate, systems::sync_removals)
494                .set_breakpoint(Update, setup_physics);
495
496            app.insert_resource(stepping);
497
498            let mut stepping = app.world_mut().resource_mut::<Stepping>();
499            // Advancing once to get the context.
500            stepping.continue_frame();
501            app.update();
502            // arbitrary hardcoded amount to run the simulation for a few frames.
503            // This test uses stepping so the actual amount of frames is this `number / breakpoints`
504            for _ in 0..20 {
505                let world = app.world_mut();
506                let stepping = world.resource_mut::<Stepping>();
507                if let Some(cursor) = &stepping.cursor() {
508                    let system = world
509                        .resource::<Schedules>()
510                        .get(cursor.0)
511                        .unwrap()
512                        .systems()
513                        .unwrap()
514                        .find(|s| NodeId::System(s.0) == cursor.1)
515                        .unwrap();
516                    println!(
517                        "next system: {}",
518                        system
519                            .1
520                            .name()
521                            .to_string()
522                            .split_terminator("::")
523                            .last()
524                            .unwrap()
525                    );
526                } else {
527                    println!("no cursor, new frame!");
528                }
529                let mut stepping = world.resource_mut::<Stepping>();
530                stepping.continue_frame();
531                app.update();
532
533                let rigidbody_set = app
534                    .world_mut()
535                    .query::<&RapierRigidBodySet>()
536                    .single(app.world())
537                    .unwrap();
538
539                println!("{:?}", &rigidbody_set.entity2body);
540            }
541            let rigidbody_set = app
542                .world_mut()
543                .query::<&RapierRigidBodySet>()
544                .single(app.world())
545                .unwrap();
546
547            assert_eq!(
548                rigidbody_set.entity2body.iter().next().unwrap().1,
549                // assert the generation is 0, that means we didn't modify it twice (due to change world detection)
550                &RigidBodyHandle(Index::from_raw_parts(0, 0))
551            );
552        }
553
554        fn main() {
555            let mut app = App::new();
556            app.add_plugins((
557                TransformPlugin,
558                TimePlugin,
559                RapierPhysicsPlugin::<NoUserData>::default(),
560            ));
561            run_test(&mut app);
562        }
563
564        pub fn setup_physics(mut commands: Commands, mut counter: Local<i32>) {
565            // run on the 3rd iteration: I believe current logic is:
566            // - 1st is to setup bevy internals
567            // - 2nd is to wait for having stepping enabled, as I couldnt get it to work for the first update.
568            // - 3rd, we're looking to test adding a rapier entity while playing, opposed to a Startup function,
569            //   which most examples are focused on.
570            let run_at = 3;
571            if *counter == run_at {
572                return;
573            }
574            *counter += 1;
575            if *counter < run_at {
576                return;
577            }
578
579            commands.spawn((
580                Transform::from_xyz(0.0, 13.0, 0.0),
581                RigidBody::Dynamic,
582                cuboid(0.5, 0.5, 0.5),
583                TestMarker,
584            ));
585        }
586    }
587
588    #[test]
589    pub fn test_sync_removal() {
590        return main();
591
592        use bevy::prelude::*;
593
594        fn run_test(app: &mut App) {
595            app.insert_resource(TimeUpdateStrategy::ManualDuration(
596                std::time::Duration::from_secs_f32(1f32 / 60f32),
597            ));
598            app.insert_resource(Time::<Fixed>::from_hz(20.0));
599
600            app.add_systems(Startup, setup_physics);
601            app.add_systems(Update, remove_rapier_entity);
602            app.add_systems(FixedUpdate, || println!("Fixed Update"));
603            app.add_systems(Update, || println!("Update"));
604            app.finish();
605            // startup
606            app.update();
607            // normal updates starting
608            // render only
609            app.update();
610            app.update();
611            // render + physics
612            app.update();
613
614            let mut context_query = app.world_mut().query::<RapierContext>();
615            let context = context_query.single(app.world()).unwrap();
616            assert_eq!(context.rigidbody_set.entity2body.len(), 1);
617
618            // render only + remove entities
619            app.update();
620            // Fixed Update hasn“t run yet, so it's a risk of not having caught the bevy removed event, which will be cleaned next frame.
621
622            let mut context_query = app.world_mut().query::<RapierContext>();
623            let context = context_query.single(app.world()).unwrap();
624
625            println!("{:?}", &context.rigidbody_set.entity2body);
626            assert_eq!(context.rigidbody_set.entity2body.len(), 0);
627        }
628
629        fn main() {
630            let mut app = App::new();
631            app.add_plugins((
632                TransformPlugin,
633                TimePlugin,
634                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
635            ));
636            run_test(&mut app);
637        }
638
639        pub fn setup_physics(mut commands: Commands) {
640            commands.spawn((
641                Transform::from_xyz(0.0, 13.0, 0.0),
642                RigidBody::Dynamic,
643                cuboid(0.5, 0.5, 0.5),
644                TestMarker,
645            ));
646            println!("spawned rapier entity");
647        }
648        pub fn remove_rapier_entity(
649            mut commands: Commands,
650            to_remove: Query<Entity, With<TestMarker>>,
651            mut counter: Local<i32>,
652        ) {
653            *counter += 1;
654            if *counter != 5 {
655                return;
656            }
657            println!("removing rapier entity");
658            for e in &to_remove {
659                commands.entity(e).despawn();
660            }
661        }
662    }
663
664    #[test]
665    fn parent_child() {
666        return main();
667
668        use bevy::prelude::*;
669
670        fn main() {
671            let mut app = App::new();
672            app.add_plugins((
673                TransformPlugin,
674                TimePlugin,
675                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
676            ));
677            run_test(&mut app);
678        }
679
680        fn run_test(app: &mut App) {
681            app.insert_resource(TimeUpdateStrategy::ManualDuration(
682                std::time::Duration::from_secs_f32(1f32 / 60f32),
683            ));
684            app.add_systems(Startup, init_rapier_configuration);
685            app.add_systems(Startup, setup_physics);
686
687            app.finish();
688            for _ in 0..100 {
689                app.update();
690            }
691
692            let mut context_query = app.world_mut().query::<RapierContext>();
693            let context = context_query.single(app.world()).unwrap();
694
695            println!("{:#?}", &context.rigidbody_set.bodies);
696        }
697
698        pub fn init_rapier_configuration(
699            mut config: Query<&mut RapierConfiguration, With<DefaultRapierContext>>,
700        ) {
701            let mut config = config.single_mut().unwrap();
702            *config = RapierConfiguration {
703                force_update_from_transform_changes: true,
704                ..RapierConfiguration::new(1f32)
705            };
706        }
707
708        pub fn setup_physics(mut commands: Commands) {
709            let parent = commands
710                .spawn(Transform::from_scale(Vec3::splat(5f32)))
711                .id();
712            let mut entity_commands = commands.spawn((
713                Collider::ball(1f32),
714                Transform::from_translation(Vec3::new(200f32, 100f32, 3f32)),
715                RigidBody::Fixed,
716            ));
717            entity_commands.insert(ChildOf(parent));
718        }
719    }
720
721    #[test]
722    fn initial_scale() {
723        return main();
724
725        use bevy::prelude::*;
726
727        fn main() {
728            let mut app = App::new();
729            app.add_plugins((
730                TransformPlugin,
731                TimePlugin,
732                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
733            ));
734            run_test(&mut app);
735        }
736
737        fn run_test(app: &mut App) {
738            app.insert_resource(TimeUpdateStrategy::ManualDuration(
739                std::time::Duration::from_secs_f32(1f32 / 60f32),
740            ));
741            app.add_systems(Update, setup_physics.run_if(run_once));
742
743            app.finish();
744
745            // Running startup
746            app.update();
747            // Running first physics setup, initializes colliders and scaling.
748            app.update();
749
750            let collider = app
751                .world_mut()
752                .query::<&Collider>()
753                .single(app.world())
754                .unwrap();
755            approx::assert_relative_eq!(collider.scale, Vect::splat(0.1), epsilon = 1.0e-5);
756
757            let mut context_query = app.world_mut().query::<RapierContext>();
758            let context = context_query.single(app.world()).unwrap();
759            let physics_ball_radius = context
760                .colliders
761                .colliders
762                .iter()
763                .next()
764                .unwrap()
765                .1
766                .shape()
767                .as_ball()
768                .unwrap()
769                .radius;
770            approx::assert_relative_eq!(physics_ball_radius, 0.1, epsilon = 0.1);
771        }
772
773        pub fn setup_physics(mut commands: Commands) {
774            commands.spawn((
775                Transform::from_translation(Vec3::new(-0.2, 0.0, 0.0)).with_scale(Vec3::splat(0.1)),
776                Collider::ball(1.0),
777            ));
778        }
779    }
780}