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::{
347 asset::AssetPlugin, mesh::MeshPlugin, world_serialization::WorldSerializationPlugin,
348 };
349 if !_app.is_plugin_added::<AssetPlugin>() {
350 _app.add_plugins(AssetPlugin::default());
351 }
352 if !_app.is_plugin_added::<MeshPlugin>() {
353 _app.add_plugins(MeshPlugin);
354 }
355 if !_app.is_plugin_added::<WorldSerializationPlugin>() {
356 _app.add_plugins(WorldSerializationPlugin);
357 }
358 }
359 }
360}
361
362#[derive(Resource, Debug, Reflect, Clone)]
366pub enum RapierContextInitialization {
367 NoAutomaticRapierContext,
374 InitializeDefaultRapierContext {
377 #[reflect(remote = IntegrationParametersWrapper)]
379 integration_parameters: IntegrationParameters,
380 rapier_configuration: RapierConfiguration,
382 },
383}
384
385impl Default for RapierContextInitialization {
386 fn default() -> Self {
387 Self::default_with_length_unit(1f32)
388 }
389}
390
391impl RapierContextInitialization {
392 pub fn default_with_length_unit(length_unit: f32) -> Self {
399 let integration_parameters = IntegrationParameters {
400 length_unit,
401 ..default()
402 };
403
404 RapierContextInitialization::InitializeDefaultRapierContext {
405 integration_parameters,
406 rapier_configuration: RapierConfiguration::new(length_unit),
407 }
408 }
409}
410
411pub fn insert_default_context(
412 mut commands: Commands,
413 initialization_data: Res<RapierContextInitialization>,
414) {
415 match initialization_data.as_ref() {
416 RapierContextInitialization::NoAutomaticRapierContext => {}
417 RapierContextInitialization::InitializeDefaultRapierContext {
418 integration_parameters,
419 rapier_configuration,
420 } => {
421 commands.spawn((
422 Name::new("Rapier Context"),
423 RapierContextSimulation {
424 integration_parameters: *integration_parameters,
425 ..RapierContextSimulation::default()
426 },
427 *rapier_configuration,
428 DefaultRapierContext,
429 ));
430 }
431 }
432}
433
434pub fn setup_rapier_configuration(
435 mut commands: Commands,
436 rapier_context: Query<(Entity, &RapierContextSimulation), Without<RapierConfiguration>>,
437) {
438 for (e, rapier_context) in rapier_context.iter() {
439 commands.entity(e).insert(RapierConfiguration::new(
440 rapier_context.integration_parameters.length_unit,
441 ));
442 }
443}
444
445#[cfg(test)]
446mod test {
447
448 use bevy::{
449 ecs::schedule::{NodeId, Stepping},
450 prelude::Component,
451 time::{TimePlugin, TimeUpdateStrategy},
452 };
453 use rapier::{data::Index, dynamics::RigidBodyHandle};
454
455 use crate::{plugin::context::*, plugin::*, prelude::*};
456
457 #[cfg(feature = "dim3")]
458 fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider {
459 Collider::cuboid(hx, hy, hz)
460 }
461 #[cfg(feature = "dim2")]
462 fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider {
463 Collider::cuboid(hx, hy)
464 }
465
466 #[derive(Component)]
467 pub struct TestMarker;
468
469 #[test]
470 pub fn hierarchy_link_propagation() {
471 return main();
472
473 use bevy::prelude::*;
474
475 fn run_test(app: &mut App) {
476 app.insert_resource(TimeUpdateStrategy::ManualDuration(
477 std::time::Duration::from_secs_f32(1f32 / 60f32),
478 ));
479
480 app.add_systems(Update, setup_physics);
481
482 app.finish();
483
484 let mut stepping = Stepping::new();
485
486 app.update();
487
488 stepping
489 .add_schedule(PostUpdate)
490 .add_schedule(Update)
491 .enable()
492 .set_breakpoint(PostUpdate, systems::on_add_entity_with_parent)
493 .set_breakpoint(PostUpdate, systems::init_rigid_bodies)
494 .set_breakpoint(PostUpdate, systems::on_change_context)
495 .set_breakpoint(PostUpdate, systems::sync_removals)
496 .set_breakpoint(Update, setup_physics);
497
498 app.insert_resource(stepping);
499
500 let mut stepping = app.world_mut().resource_mut::<Stepping>();
501 stepping.continue_frame();
503 app.update();
504 for _ in 0..20 {
507 let world = app.world_mut();
508 let stepping = world.resource_mut::<Stepping>();
509 if let Some(cursor) = &stepping.cursor() {
510 let system = world
511 .resource::<Schedules>()
512 .get(cursor.0)
513 .unwrap()
514 .systems()
515 .unwrap()
516 .find(|s| NodeId::System(s.0) == cursor.1)
517 .unwrap();
518 println!(
519 "next system: {}",
520 system
521 .1
522 .name()
523 .to_string()
524 .split_terminator("::")
525 .last()
526 .unwrap()
527 );
528 } else {
529 println!("no cursor, new frame!");
530 }
531 let mut stepping = world.resource_mut::<Stepping>();
532 stepping.continue_frame();
533 app.update();
534
535 let rigidbody_set = app
536 .world_mut()
537 .query::<&RapierRigidBodySet>()
538 .single(app.world())
539 .unwrap();
540
541 println!("{:?}", &rigidbody_set.entity2body);
542 }
543 let rigidbody_set = app
544 .world_mut()
545 .query::<&RapierRigidBodySet>()
546 .single(app.world())
547 .unwrap();
548
549 assert_eq!(
550 rigidbody_set.entity2body.iter().next().unwrap().1,
551 &RigidBodyHandle(Index::from_raw_parts(0, 0))
553 );
554 }
555
556 fn main() {
557 let mut app = App::new();
558 app.add_plugins((
559 TransformPlugin,
560 TimePlugin,
561 RapierPhysicsPlugin::<NoUserData>::default(),
562 ));
563 run_test(&mut app);
564 }
565
566 pub fn setup_physics(mut commands: Commands, mut counter: Local<i32>) {
567 let run_at = 3;
573 if *counter == run_at {
574 return;
575 }
576 *counter += 1;
577 if *counter < run_at {
578 return;
579 }
580
581 commands.spawn((
582 Transform::from_xyz(0.0, 13.0, 0.0),
583 RigidBody::Dynamic,
584 cuboid(0.5, 0.5, 0.5),
585 TestMarker,
586 ));
587 }
588 }
589
590 #[test]
591 pub fn test_sync_removal() {
592 return main();
593
594 use bevy::prelude::*;
595
596 fn run_test(app: &mut App) {
597 app.insert_resource(TimeUpdateStrategy::ManualDuration(
598 std::time::Duration::from_secs_f32(1f32 / 60f32),
599 ));
600 app.insert_resource(Time::<Fixed>::from_hz(20.0));
601
602 app.add_systems(Startup, setup_physics);
603 app.add_systems(Update, remove_rapier_entity);
604 app.add_systems(FixedUpdate, || println!("Fixed Update"));
605 app.add_systems(Update, || println!("Update"));
606 app.finish();
607 app.update();
609 app.update();
612 app.update();
613 app.update();
615
616 let mut context_query = app.world_mut().query::<RapierContext>();
617 let context = context_query.single(app.world()).unwrap();
618 assert_eq!(context.rigidbody_set.entity2body.len(), 1);
619
620 app.update();
622 let mut context_query = app.world_mut().query::<RapierContext>();
625 let context = context_query.single(app.world()).unwrap();
626
627 println!("{:?}", &context.rigidbody_set.entity2body);
628 assert_eq!(context.rigidbody_set.entity2body.len(), 0);
629 }
630
631 fn main() {
632 let mut app = App::new();
633 app.add_plugins((
634 TransformPlugin,
635 TimePlugin,
636 RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
637 ));
638 run_test(&mut app);
639 }
640
641 pub fn setup_physics(mut commands: Commands) {
642 commands.spawn((
643 Transform::from_xyz(0.0, 13.0, 0.0),
644 RigidBody::Dynamic,
645 cuboid(0.5, 0.5, 0.5),
646 TestMarker,
647 ));
648 println!("spawned rapier entity");
649 }
650 pub fn remove_rapier_entity(
651 mut commands: Commands,
652 to_remove: Query<Entity, With<TestMarker>>,
653 mut counter: Local<i32>,
654 ) {
655 *counter += 1;
656 if *counter != 5 {
657 return;
658 }
659 println!("removing rapier entity");
660 for e in &to_remove {
661 commands.entity(e).despawn();
662 }
663 }
664 }
665
666 #[test]
667 fn parent_child() {
668 return main();
669
670 use bevy::prelude::*;
671
672 fn main() {
673 let mut app = App::new();
674 app.add_plugins((
675 TransformPlugin,
676 TimePlugin,
677 RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
678 ));
679 run_test(&mut app);
680 }
681
682 fn run_test(app: &mut App) {
683 app.insert_resource(TimeUpdateStrategy::ManualDuration(
684 std::time::Duration::from_secs_f32(1f32 / 60f32),
685 ));
686 app.add_systems(Startup, init_rapier_configuration);
687 app.add_systems(Startup, setup_physics);
688
689 app.finish();
690 for _ in 0..100 {
691 app.update();
692 }
693
694 let mut context_query = app.world_mut().query::<RapierContext>();
695 let context = context_query.single(app.world()).unwrap();
696
697 println!("{:#?}", &context.rigidbody_set.bodies);
698 }
699
700 pub fn init_rapier_configuration(
701 mut config: Query<&mut RapierConfiguration, With<DefaultRapierContext>>,
702 ) {
703 let mut config = config.single_mut().unwrap();
704 *config = RapierConfiguration {
705 force_update_from_transform_changes: true,
706 ..RapierConfiguration::new(1f32)
707 };
708 }
709
710 pub fn setup_physics(mut commands: Commands) {
711 let parent = commands
712 .spawn(Transform::from_scale(Vec3::splat(5f32)))
713 .id();
714 let mut entity_commands = commands.spawn((
715 Collider::ball(1f32),
716 Transform::from_translation(Vec3::new(200f32, 100f32, 3f32)),
717 RigidBody::Fixed,
718 ));
719 entity_commands.insert(ChildOf(parent));
720 }
721 }
722
723 #[test]
724 fn initial_scale() {
725 return main();
726
727 use bevy::prelude::*;
728
729 fn main() {
730 let mut app = App::new();
731 app.add_plugins((
732 TransformPlugin,
733 TimePlugin,
734 RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule(),
735 ));
736 run_test(&mut app);
737 }
738
739 fn run_test(app: &mut App) {
740 app.insert_resource(TimeUpdateStrategy::ManualDuration(
741 std::time::Duration::from_secs_f32(1f32 / 60f32),
742 ));
743 app.add_systems(Update, setup_physics.run_if(run_once));
744
745 app.finish();
746
747 app.update();
749 app.update();
751
752 let collider = app
753 .world_mut()
754 .query::<&Collider>()
755 .single(app.world())
756 .unwrap();
757 approx::assert_relative_eq!(collider.scale, Vect::splat(0.1), epsilon = 1.0e-5);
758
759 let mut context_query = app.world_mut().query::<RapierContext>();
760 let context = context_query.single(app.world()).unwrap();
761 let physics_ball_radius = context
762 .colliders
763 .colliders
764 .iter()
765 .next()
766 .unwrap()
767 .1
768 .shape()
769 .as_ball()
770 .unwrap()
771 .radius;
772 approx::assert_relative_eq!(physics_ball_radius, 0.1, epsilon = 0.1);
773 }
774
775 pub fn setup_physics(mut commands: Commands) {
776 commands.spawn((
777 Transform::from_translation(Vec3::new(-0.2, 0.0, 0.0)).with_scale(Vec3::splat(0.1)),
778 Collider::ball(1.0),
779 ));
780 }
781 }
782}