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
20pub type NoUserData = ();
22
23pub struct RapierPhysicsPlugin<PhysicsHooks = ()> {
28 schedule: Interned<dyn ScheduleLabel>,
29 default_system_setup: bool,
30 default_world_setup: RapierContextInitialization,
34 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 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 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 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 #[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 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 pub fn in_fixed_schedule(self) -> Self {
113 self.in_schedule(FixedUpdate)
114 }
115
116 pub fn in_schedule(mut self, schedule: impl ScheduleLabel) -> Self {
118 self.schedule = schedule.intern();
119 self
120 }
121
122 pub fn get_systems(set: PhysicsSet) -> ScheduleConfigs<ScheduleSystem> {
126 match set {
127 PhysicsSet::SyncBackend => (
128 (
129 setup_rapier_configuration,
133 systems::update_character_controls,
135 )
136 .chain()
137 .in_set(PhysicsSet::SyncBackend),
138 (
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 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 systems::writeback_mass_properties.ambiguous_with(systems::writeback_rigid_bodies),
184 )
185 .in_set(PhysicsSet::Writeback)
186 .into_configs(),
187 }
188 }
189}
190
191#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
193pub struct RapierBevyComponentApply;
194
195#[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#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
219pub enum PhysicsSet {
220 SyncBackend,
224 StepSimulation,
228 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 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 if self.schedule != PostUpdate.intern() {
293 app.add_systems(
294 PostUpdate,
295 (systems::sync_removals,).before(TransformSystems::Propagate),
296 );
297 }
298
299 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 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#[derive(Resource, Debug, Reflect, Clone)]
364pub enum RapierContextInitialization {
365 NoAutomaticRapierContext,
372 InitializeDefaultRapierContext {
375 #[reflect(remote = IntegrationParametersWrapper)]
377 integration_parameters: IntegrationParameters,
378 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 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 stepping.continue_frame();
501 app.update();
502 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 &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 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 app.update();
607 app.update();
610 app.update();
611 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 app.update();
620 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 app.update();
747 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}