Skip to main content

bevy_rapier2d/plugin/
plugin.rs

1use crate::pipeline::{CollisionEvent, ContactForceEvent};
2use crate::prelude::*;
3use crate::reflect::{IntegrationParametersWrapper, JointAxisWrapper, 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::reset_removed_soft_body_cluster_components,
150                        systems::sync_removals,
151                        systems::reset_removed_rigid_body_components,
152                        systems::reset_removed_collider_components,
153                        systems::sync_soft_body_removals,
154                        systems::reset_removed_soft_body_components,
155                        #[cfg(all(feature = "dim3", feature = "async-collider"))]
156                        systems::init_async_scene_colliders,
157                        #[cfg(all(feature = "dim3", feature = "async-collider"))]
158                        systems::init_async_colliders,
159                        systems::init_rigid_bodies,
160                        systems::init_soft_bodies,
161                        systems::init_soft_body_clusters,
162                        systems::init_colliders,
163                        systems::init_joints,
164                    )
165                        .chain()
166                        .in_set(PhysicsSet::SyncBackend),
167                    (
168                        (
169                            systems::apply_scale.in_set(RapierBevyComponentApply),
170                            systems::apply_collider_user_changes.in_set(RapierBevyComponentApply),
171                        )
172                            .chain(),
173                        systems::apply_joint_user_changes.in_set(RapierBevyComponentApply),
174                    ),
175                    // TODO: joints and colliders might be parallelizable.
176                    systems::apply_multibody_joint_properties.in_set(RapierBevyComponentApply),
177                    systems::apply_initial_rigid_body_impulses.in_set(RapierBevyComponentApply),
178                    systems::apply_rigid_body_user_changes.in_set(RapierBevyComponentApply),
179                    systems::apply_soft_body_user_changes.in_set(RapierBevyComponentApply),
180                    systems::apply_soft_body_cluster_changes.in_set(RapierBevyComponentApply),
181                    // Controllers act on the rigid-bodies' up-to-date state, right before the step.
182                    systems::update_pid_controllers.in_set(RapierBevyComponentApply),
183                    #[cfg(feature = "dim3")]
184                    systems::update_vehicle_controllers.in_set(RapierBevyComponentApply),
185                )
186                    .chain(),
187            )
188                .chain()
189                .into_configs(),
190            PhysicsSet::StepSimulation => (systems::step_simulation::<PhysicsHooks>)
191                .in_set(PhysicsSet::StepSimulation)
192                .into_configs(),
193            PhysicsSet::Writeback => (
194                systems::update_colliding_entities,
195                systems::writeback_rigid_bodies,
196                // Each writeback write to different properties.
197                systems::writeback_mass_properties.ambiguous_with(systems::writeback_rigid_bodies),
198                systems::writeback_collider_mass_properties,
199                systems::writeback_impulse_joint_impulses,
200                systems::writeback_multibody_joint_states,
201                (
202                    systems::handle_soft_body_tears,
203                    systems::writeback_soft_bodies,
204                    // Modified meshes must emit their asset events this frame.
205                    #[cfg(feature = "to-bevy-mesh")]
206                    systems::sync_soft_body_meshes.before(bevy::asset::AssetEventSystems),
207                )
208                    .chain()
209                    .after(systems::writeback_rigid_bodies)
210                    .after(systems::writeback_mass_properties)
211                    .after(systems::writeback_collider_mass_properties)
212                    .after(systems::writeback_impulse_joint_impulses)
213                    .after(systems::writeback_multibody_joint_states),
214            )
215                .in_set(PhysicsSet::Writeback)
216                .into_configs(),
217        }
218    }
219}
220
221/// A set for rapier's copying bevy_rapier's Bevy components back into rapier.
222#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
223pub struct RapierBevyComponentApply;
224
225/// A set for rapier's copy of Bevy's transform propagation systems.
226///
227/// See [`TransformSystems`](bevy::transform::TransformSystems::Propagate).
228#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
229pub struct RapierTransformPropagateSet;
230
231impl<PhysicsHooksSystemParam> Default for RapierPhysicsPlugin<PhysicsHooksSystemParam> {
232    fn default() -> Self {
233        Self {
234            schedule: PostUpdate.intern(),
235            default_system_setup: true,
236            default_world_setup: Default::default(),
237            enabled_physics_schedules: HashSet::from_iter([
238                PhysicsSet::SyncBackend,
239                PhysicsSet::StepSimulation,
240                PhysicsSet::Writeback,
241            ]),
242            _phantom: PhantomData,
243        }
244    }
245}
246
247/// [`SystemSet`] for each phase of the plugin.
248#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
249pub enum PhysicsSet {
250    /// This set runs the systems responsible for synchronizing (and
251    /// initializing) backend data structures with current component state.
252    /// These systems typically run at the after [`Update`].
253    SyncBackend,
254    /// The systems responsible for advancing the physics simulation, and
255    /// updating the internal state for scene queries.
256    /// These systems typically run immediately after [`PhysicsSet::SyncBackend`].
257    StepSimulation,
258    /// The systems responsible for updating
259    /// [`crate::geometry::CollidingEntities`] and writing
260    /// the result of the last simulation step into our `bevy_rapier`
261    /// components and the [`GlobalTransform`] component.
262    /// These systems typically run immediately after [`PhysicsSet::StepSimulation`].
263    Writeback,
264}
265
266impl<PhysicsHooks> Plugin for RapierPhysicsPlugin<PhysicsHooks>
267where
268    PhysicsHooks: 'static + BevyPhysicsHooks,
269    for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
270{
271    fn build(&self, app: &mut App) {
272        // Register components as reflectable.
273        app.register_type::<RigidBody>()
274            .register_type::<Velocity>()
275            .register_type::<AdditionalMassProperties>()
276            .register_type::<MassProperties>()
277            .register_type::<LockedAxes>()
278            .register_type::<ExternalForce>()
279            .register_type::<ExternalImpulse>()
280            .register_type::<Sleeping>()
281            .register_type::<Damping>()
282            .register_type::<Dominance>()
283            .register_type::<Ccd>()
284            .register_type::<SoftCcd>()
285            .register_type::<GravityScale>()
286            .register_type::<ReadWorldMassProperties>()
287            .register_type::<AdditionalSolverIterations>()
288            .register_type::<AdditionalPgsIterations>()
289            .register_type::<AllowFastRotation>()
290            .register_type::<CollidingEntities>()
291            .register_type::<Sensor>()
292            .register_type::<Friction>()
293            .register_type::<Restitution>()
294            .register_type::<CollisionGroups>()
295            .register_type::<SolverGroups>()
296            .register_type::<ContactForceEventThreshold>()
297            .register_type::<ContactSkin>()
298            .register_type::<ReadColliderMassProperties>()
299            .register_type::<InteractionTestMode>()
300            .register_type::<Group>()
301            .register_type::<RapierContextEntityLink>()
302            .register_type::<RapierConfiguration>()
303            .register_type::<SimulationMode>()
304            .register_type::<BroadPhaseOptimizationStrategy>()
305            .register_type::<SimulationToRenderTime>()
306            .register_type::<DefaultRapierContext>()
307            .register_type::<RapierContextInitialization>()
308            .register_type::<ImpulseJointDisabled>()
309            .register_type::<ImpulseJointImpulses>()
310            .register_type::<KinematicMultibodyJoint>()
311            .register_type::<MultibodyJointDamping>()
312            .register_type::<MultibodyJointFriction>()
313            .register_type::<MultibodyJointArmature>()
314            .register_type::<MultibodyJointSprings>()
315            .register_type::<MultibodyJointCoupling>()
316            .register_type::<MultibodyJointCouplings>()
317            .register_type::<MultibodySelfContactsDisabled>()
318            .register_type::<MultibodyJointState>()
319            .register_type::<SpringCoefficientsWrapper>()
320            .register_type::<JointAxisWrapper>()
321            .register_type::<AxesMask>()
322            .register_type::<PidTarget>()
323            .register_type::<ControllerIgnored>()
324            .register_type::<PdController>()
325            .register_type::<PidController>()
326            .register_type::<SoftBodyMaterial>()
327            .register_type::<SoftBodyDisabled>()
328            .register_type::<SoftBodyPinnedParticles>()
329            .register_type::<SoftBodyKinematicTargets>()
330            .register_type::<SoftBodyAttachment>()
331            .register_type::<SoftBodyAttachments>()
332            .register_type::<SoftBodyExternalForce>()
333            .register_type::<SoftBodyExternalImpulse>()
334            .register_type::<SoftBodyVolumeFactor>()
335            .register_type::<SoftBodyState>()
336            .register_type::<SoftBodyCluster>()
337            .register_type::<SoftBodyClusterPinned>()
338            .register_type::<SoftBodyClusterKinematicTarget>()
339            .register_type::<SoftBodyClusterShapeMatching>()
340            .register_type::<crate::reflect::SoftBodiesSettingsWrapper>()
341            .register_type::<crate::reflect::SoftRecoverySettingsWrapper>()
342            .register_type::<crate::reflect::SoftPatchConstraintsWrapper>()
343            .register_type::<crate::reflect::SoftBodyMaterialWrapper>()
344            .register_type::<crate::reflect::SoftEdgePlasticFlowWrapper>();
345        #[cfg(feature = "fem")]
346        app.register_type::<crate::reflect::SoftFemParametersWrapper>();
347        #[cfg(feature = "dim3")]
348        app.register_type::<GyroscopicForces>()
349            .register_type::<crate::reflect::WheelTuningWrapper>()
350            .register_type::<VehicleWheelState>()
351            .register_type::<VehicleWheel>()
352            .register_type::<RayCastVehicleController>();
353
354        app.insert_resource(Messages::<CollisionEvent>::default())
355            .insert_resource(Messages::<ContactForceEvent>::default())
356            .insert_resource(Messages::<MassModifiedEvent>::default())
357            .add_message::<PhysicsQuarantineEvent>()
358            .add_message::<SoftBodyTearEvent>();
359        let default_world_init = app.world().get_resource::<RapierContextInitialization>();
360        if let Some(world_init) = default_world_init {
361            log::warn!("RapierPhysicsPlugin added but a `RapierContextInitialization` resource was already existing.\
362            This might overwrite previous configuration made via `RapierPhysicsPlugin::with_custom_initialization`\
363            or `RapierPhysicsPlugin::with_length_unit`.
364            The following resource will be used: {world_init:?}");
365        } else {
366            app.insert_resource(self.default_world_setup.clone());
367        }
368
369        app.add_systems(
370            PreStartup,
371            (insert_default_context, setup_rapier_configuration).chain(),
372        );
373
374        // These *must* be in the main schedule currently so that they do not miss events.
375        // See test `test_sync_removal` for an example of this.
376        if self.schedule != PostUpdate.intern() {
377            app.add_systems(
378                PostUpdate,
379                (
380                    systems::reset_removed_soft_body_cluster_components,
381                    systems::sync_removals,
382                    systems::reset_removed_rigid_body_components,
383                    systems::reset_removed_collider_components,
384                    systems::sync_soft_body_removals,
385                    systems::reset_removed_soft_body_components,
386                )
387                    .chain()
388                    .before(TransformSystems::Propagate),
389            );
390        }
391
392        // Add each set as necessary
393        if self.default_system_setup {
394            app.configure_sets(
395                self.schedule,
396                (
397                    PhysicsSet::SyncBackend,
398                    PhysicsSet::StepSimulation,
399                    PhysicsSet::Writeback,
400                )
401                    .chain()
402                    .before(TransformSystems::Propagate),
403            );
404            app.configure_sets(
405                self.schedule,
406                RapierTransformPropagateSet.in_set(PhysicsSet::SyncBackend),
407            );
408            app.configure_sets(
409                self.schedule,
410                RapierBevyComponentApply.in_set(PhysicsSet::SyncBackend),
411            );
412            let mut add_systems_if_enabled = |physics_set: PhysicsSet| {
413                if self.enabled_physics_schedules.contains(&physics_set) {
414                    app.add_systems(self.schedule, Self::get_systems(physics_set));
415                }
416            };
417            add_systems_if_enabled(PhysicsSet::SyncBackend);
418            add_systems_if_enabled(PhysicsSet::Writeback);
419            add_systems_if_enabled(PhysicsSet::StepSimulation);
420
421            app.init_resource::<TimestepMode>();
422
423            // Warn user if the timestep mode isn't in Fixed
424            if self.schedule.dyn_eq(&FixedUpdate as &dyn DynEq) {
425                let config = app.world_mut().resource::<TimestepMode>();
426                match config {
427                    TimestepMode::Fixed { .. } => {}
428                    mode => {
429                        log::warn!("TimestepMode is set to `{mode:?}`, it is recommended to use `TimestepMode::Fixed` if you have the physics in `FixedUpdate`");
430                    }
431                }
432            }
433        }
434    }
435
436    fn finish(&self, _app: &mut App) {
437        #[cfg(all(feature = "dim3", feature = "async-collider"))]
438        {
439            use bevy::{
440                asset::AssetPlugin, mesh::MeshPlugin, world_serialization::WorldSerializationPlugin,
441            };
442            if !_app.is_plugin_added::<AssetPlugin>() {
443                _app.add_plugins(AssetPlugin::default());
444            }
445            if !_app.is_plugin_added::<MeshPlugin>() {
446                _app.add_plugins(MeshPlugin);
447            }
448            if !_app.is_plugin_added::<WorldSerializationPlugin>() {
449                _app.add_plugins(WorldSerializationPlugin);
450            }
451        }
452    }
453}
454
455/// Specifies a default configuration for the default [`RapierContext`]
456///
457/// Designed to be passed as parameter to [`RapierPhysicsPlugin::with_custom_initialization`].
458#[derive(Resource, Debug, Reflect, Clone)]
459#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
460#[allow(clippy::large_enum_variant)] // Only a single instance of this resource exists.
461pub enum RapierContextInitialization {
462    /// [`RapierPhysicsPlugin`] will not spawn any entity containing [`RapierContextSimulation`] automatically.
463    ///
464    /// You are responsible for creating a [`RapierContextSimulation`],
465    /// before spawning any rapier entities (rigidbodies, colliders, joints).
466    ///
467    /// You might be interested in adding [`DefaultRapierContext`] to the created physics context.
468    NoAutomaticRapierContext,
469    /// [`RapierPhysicsPlugin`] will spawn an entity containing a [`RapierContextSimulation`]
470    /// automatically during [`PreStartup`], with the [`DefaultRapierContext`] marker component.
471    InitializeDefaultRapierContext {
472        /// Integration parameters component which will be added to the default rapier context.
473        #[reflect(remote = IntegrationParametersWrapper)]
474        integration_parameters: IntegrationParameters,
475        /// Rapier configuration component which will be added to the default rapier context.
476        rapier_configuration: RapierConfiguration,
477        /// The strategy used to keep the broad-phase BVH of the default rapier context efficient.
478        broad_phase_optimization_strategy: BroadPhaseOptimizationStrategy,
479    },
480}
481
482impl Default for RapierContextInitialization {
483    fn default() -> Self {
484        Self::default_with_length_unit(1f32)
485    }
486}
487
488impl RapierContextInitialization {
489    /// Configures rapier with the specified length unit.
490    ///
491    /// See the documentation of [`IntegrationParameters::length_unit`] for additional details
492    /// on that argument.
493    ///
494    /// The default gravity is automatically scaled by that length unit.
495    pub fn default_with_length_unit(length_unit: f32) -> Self {
496        let integration_parameters = IntegrationParameters {
497            length_unit,
498            ..default()
499        };
500
501        RapierContextInitialization::InitializeDefaultRapierContext {
502            integration_parameters,
503            rapier_configuration: RapierConfiguration::new(length_unit),
504            broad_phase_optimization_strategy: BroadPhaseOptimizationStrategy::default(),
505        }
506    }
507}
508
509pub fn insert_default_context(
510    mut commands: Commands,
511    initialization_data: Res<RapierContextInitialization>,
512) {
513    match initialization_data.as_ref() {
514        RapierContextInitialization::NoAutomaticRapierContext => {}
515        RapierContextInitialization::InitializeDefaultRapierContext {
516            integration_parameters,
517            rapier_configuration,
518            broad_phase_optimization_strategy,
519        } => {
520            commands.spawn((
521                Name::new("Rapier Context"),
522                RapierContextSimulation {
523                    integration_parameters: *integration_parameters,
524                    ..RapierContextSimulation::with_broad_phase_optimization_strategy(
525                        *broad_phase_optimization_strategy,
526                    )
527                },
528                *rapier_configuration,
529                DefaultRapierContext,
530            ));
531        }
532    }
533}
534
535pub fn setup_rapier_configuration(
536    mut commands: Commands,
537    rapier_context: Query<(Entity, &RapierContextSimulation), Without<RapierConfiguration>>,
538) {
539    for (e, rapier_context) in rapier_context.iter() {
540        commands.entity(e).insert(RapierConfiguration::new(
541            rapier_context.integration_parameters.length_unit,
542        ));
543    }
544}
545
546#[cfg(test)]
547mod test {
548
549    use bevy::{
550        ecs::schedule::{NodeId, Stepping},
551        prelude::Component,
552        time::{TimePlugin, TimeUpdateStrategy},
553    };
554    use rapier::{data::Index, dynamics::RigidBodyHandle};
555
556    use crate::{plugin::context::*, plugin::*, prelude::*};
557
558    #[cfg(feature = "dim3")]
559    fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
560        Collider::cuboid(hx, hy, hz)
561    }
562    #[cfg(feature = "dim2")]
563    fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
564        Collider::cuboid(hx, hy)
565    }
566
567    #[derive(Component)]
568    pub struct TestMarker;
569
570    #[test]
571    pub fn hierarchy_link_propagation() {
572        return main();
573
574        use bevy::prelude::*;
575
576        fn run_test(app: &mut App) {
577            app.insert_resource(TimeUpdateStrategy::ManualDuration(
578                std::time::Duration::from_secs_f32(1f32 / 60f32),
579            ));
580
581            app.add_systems(Update, setup_physics);
582
583            app.finish();
584
585            let mut stepping = Stepping::new();
586
587            app.update();
588
589            stepping
590                .add_schedule(PostUpdate)
591                .add_schedule(Update)
592                .enable()
593                .set_breakpoint(PostUpdate, systems::on_add_entity_with_parent)
594                .set_breakpoint(PostUpdate, systems::init_rigid_bodies)
595                .set_breakpoint(PostUpdate, systems::on_change_context)
596                .set_breakpoint(PostUpdate, systems::sync_removals)
597                .set_breakpoint(Update, setup_physics);
598
599            app.insert_resource(stepping);
600
601            let mut stepping = app.world_mut().resource_mut::<Stepping>();
602            // Advancing once to get the context.
603            stepping.continue_frame();
604            app.update();
605            // arbitrary hardcoded amount to run the simulation for a few frames.
606            // This test uses stepping so the actual amount of frames is this `number / breakpoints`
607            for _ in 0..20 {
608                let world = app.world_mut();
609                let stepping = world.resource_mut::<Stepping>();
610                if let Some(cursor) = &stepping.cursor() {
611                    let system = world
612                        .resource::<Schedules>()
613                        .get(cursor.0)
614                        .unwrap()
615                        .systems()
616                        .unwrap()
617                        .find(|s| NodeId::System(s.0) == cursor.1)
618                        .unwrap();
619                    println!(
620                        "next system: {}",
621                        system
622                            .1
623                            .name()
624                            .to_string()
625                            .split_terminator("::")
626                            .last()
627                            .unwrap()
628                    );
629                } else {
630                    println!("no cursor, new frame!");
631                }
632                let mut stepping = world.resource_mut::<Stepping>();
633                stepping.continue_frame();
634                app.update();
635
636                let rigidbody_set = app
637                    .world_mut()
638                    .query::<&RapierRigidBodySet>()
639                    .single(app.world())
640                    .unwrap();
641
642                println!("{:?}", rigidbody_set.entity2body);
643            }
644            let rigidbody_set = app
645                .world_mut()
646                .query::<&RapierRigidBodySet>()
647                .single(app.world())
648                .unwrap();
649
650            assert_eq!(
651                rigidbody_set.entity2body.iter().next().unwrap().1,
652                // assert the generation is 0, that means we didn't modify it twice (due to change world detection)
653                &RigidBodyHandle(Index::from_raw_parts(0, 0))
654            );
655        }
656
657        fn main() {
658            let mut app = App::new();
659            app.add_plugins((
660                TransformPlugin,
661                TimePlugin,
662                RapierPhysicsPlugin::<NoUserData>::default(),
663            ));
664            run_test(&mut app);
665        }
666
667        pub fn setup_physics(mut commands: Commands, mut counter: Local<i32>) {
668            // run on the 3rd iteration: I believe current logic is:
669            // - 1st is to setup bevy internals
670            // - 2nd is to wait for having stepping enabled, as I couldnt get it to work for the first update.
671            // - 3rd, we're looking to test adding a rapier entity while playing, opposed to a Startup function,
672            //   which most examples are focused on.
673            let run_at = 3;
674            if *counter == run_at {
675                return;
676            }
677            *counter += 1;
678            if *counter < run_at {
679                return;
680            }
681
682            commands.spawn((
683                Transform::from_xyz(0.0, 13.0, 0.0),
684                RigidBody::Dynamic,
685                cuboid(0.5, 0.5, 0.5),
686                TestMarker,
687            ));
688        }
689    }
690
691    #[test]
692    pub fn test_sync_removal() {
693        return main();
694
695        use bevy::prelude::*;
696
697        fn run_test(app: &mut App) {
698            app.insert_resource(TimeUpdateStrategy::ManualDuration(
699                std::time::Duration::from_secs_f32(1f32 / 60f32),
700            ));
701            app.insert_resource(Time::<Fixed>::from_hz(20.0));
702
703            app.add_systems(Startup, setup_physics);
704            app.add_systems(Update, remove_rapier_entity);
705            app.add_systems(FixedUpdate, || println!("Fixed Update"));
706            app.add_systems(Update, || println!("Update"));
707            app.finish();
708            // startup
709            app.update();
710            // normal updates starting
711            // render only
712            app.update();
713            app.update();
714            // render + physics
715            app.update();
716
717            let mut context_query = app.world_mut().query::<RapierContext>();
718            let context = context_query.single(app.world()).unwrap();
719            assert_eq!(context.rigidbody_set.entity2body.len(), 1);
720
721            // render only + remove entities
722            app.update();
723            // 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.
724
725            let mut context_query = app.world_mut().query::<RapierContext>();
726            let context = context_query.single(app.world()).unwrap();
727
728            println!("{:?}", context.rigidbody_set.entity2body);
729            assert_eq!(context.rigidbody_set.entity2body.len(), 0);
730        }
731
732        fn main() {
733            let mut app = App::new();
734            app.add_plugins((
735                TransformPlugin,
736                TimePlugin,
737                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
738            ));
739            run_test(&mut app);
740        }
741
742        pub fn setup_physics(mut commands: Commands) {
743            commands.spawn((
744                Transform::from_xyz(0.0, 13.0, 0.0),
745                RigidBody::Dynamic,
746                cuboid(0.5, 0.5, 0.5),
747                TestMarker,
748            ));
749            println!("spawned rapier entity");
750        }
751        pub fn remove_rapier_entity(
752            mut commands: Commands,
753            to_remove: Query<Entity, With<TestMarker>>,
754            mut counter: Local<i32>,
755        ) {
756            *counter += 1;
757            if *counter != 5 {
758                return;
759            }
760            println!("removing rapier entity");
761            for e in &to_remove {
762                commands.entity(e).despawn();
763            }
764        }
765    }
766
767    #[test]
768    fn parent_child() {
769        return main();
770
771        use bevy::prelude::*;
772
773        fn main() {
774            let mut app = App::new();
775            app.add_plugins((
776                TransformPlugin,
777                TimePlugin,
778                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
779            ));
780            run_test(&mut app);
781        }
782
783        fn run_test(app: &mut App) {
784            app.insert_resource(TimeUpdateStrategy::ManualDuration(
785                std::time::Duration::from_secs_f32(1f32 / 60f32),
786            ));
787            app.add_systems(Startup, init_rapier_configuration);
788            app.add_systems(Startup, setup_physics);
789
790            app.finish();
791            for _ in 0..100 {
792                app.update();
793            }
794
795            let mut context_query = app.world_mut().query::<RapierContext>();
796            let context = context_query.single(app.world()).unwrap();
797
798            println!("{:#?}", context.rigidbody_set.bodies);
799        }
800
801        pub fn init_rapier_configuration(
802            mut config: Query<&mut RapierConfiguration, With<DefaultRapierContext>>,
803        ) {
804            let mut config = config.single_mut().unwrap();
805            *config = RapierConfiguration {
806                force_update_from_transform_changes: true,
807                ..RapierConfiguration::new(1f32)
808            };
809        }
810
811        pub fn setup_physics(mut commands: Commands) {
812            let parent = commands
813                .spawn(Transform::from_scale(Vec3::splat(5f32)))
814                .id();
815            let mut entity_commands = commands.spawn((
816                Collider::ball(1f32),
817                Transform::from_translation(Vec3::new(200f32, 100f32, 3f32)),
818                RigidBody::Fixed,
819            ));
820            entity_commands.insert(ChildOf(parent));
821        }
822    }
823
824    #[test]
825    fn initial_scale() {
826        return main();
827
828        use bevy::prelude::*;
829
830        fn main() {
831            let mut app = App::new();
832            app.add_plugins((
833                TransformPlugin,
834                TimePlugin,
835                RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
836            ));
837            run_test(&mut app);
838        }
839
840        fn run_test(app: &mut App) {
841            app.insert_resource(TimeUpdateStrategy::ManualDuration(
842                std::time::Duration::from_secs_f32(1f32 / 60f32),
843            ));
844            app.add_systems(Update, setup_physics.run_if(run_once));
845
846            app.finish();
847
848            // Running startup
849            app.update();
850            // Running first physics setup, initializes colliders and scaling.
851            app.update();
852
853            let collider = app
854                .world_mut()
855                .query::<&Collider>()
856                .single(app.world())
857                .unwrap();
858            approx::assert_relative_eq!(collider.scale, Vect::splat(0.1), epsilon = 1.0e-5);
859
860            let mut context_query = app.world_mut().query::<RapierContext>();
861            let context = context_query.single(app.world()).unwrap();
862            let physics_ball_radius = context
863                .colliders
864                .colliders
865                .iter()
866                .next()
867                .unwrap()
868                .1
869                .shape()
870                .as_ball()
871                .unwrap()
872                .radius;
873            approx::assert_relative_eq!(physics_ball_radius, 0.1, epsilon = 0.1);
874        }
875
876        pub fn setup_physics(mut commands: Commands) {
877            commands.spawn((
878                Transform::from_translation(Vec3::new(-0.2, 0.0, 0.0)).with_scale(Vec3::splat(0.1)),
879                Collider::ball(1.0),
880            ));
881        }
882    }
883}