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
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::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 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 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 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 #[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#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
223pub struct RapierBevyComponentApply;
224
225#[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#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
249pub enum PhysicsSet {
250 SyncBackend,
254 StepSimulation,
258 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 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 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 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 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#[derive(Resource, Debug, Reflect, Clone)]
459#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
460#[allow(clippy::large_enum_variant)] pub enum RapierContextInitialization {
462 NoAutomaticRapierContext,
469 InitializeDefaultRapierContext {
472 #[reflect(remote = IntegrationParametersWrapper)]
474 integration_parameters: IntegrationParameters,
475 rapier_configuration: RapierConfiguration,
477 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 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 stepping.continue_frame();
604 app.update();
605 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 &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 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 app.update();
710 app.update();
713 app.update();
714 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 app.update();
723 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 app.update();
850 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}