1use crate::dynamics::RapierRigidBodyHandle;
2use crate::plugin::context::systemparams::RAPIER_CONTEXT_EXPECT_ERROR;
3use crate::plugin::context::{
4 RapierContextColliders, RapierContextEntityLink, RapierContextSimulation, RapierRigidBodySet,
5};
6use crate::plugin::{configuration::TimestepMode, RapierConfiguration};
7use crate::{dynamics::RigidBody, plugin::context::SimulationToRenderTime};
8use crate::{prelude::*, utils};
9use bevy::prelude::*;
10
11use super::RapierContextLinkResolver;
12use rapier::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
13use std::collections::HashMap;
14
15pub type RigidBodyWritebackComponents<'a> = (
17 &'a RapierRigidBodyHandle,
18 &'a RapierContextEntityLink,
19 Option<&'a ChildOf>,
20 Option<&'a mut Transform>,
21 Option<&'a mut TransformInterpolation>,
22 Option<&'a mut Velocity>,
23 Option<&'a mut Sleeping>,
24 Option<&'a mut ReadWorldMassProperties>,
25);
26
27pub type RigidBodyComponents<'a> = (
29 (Entity, Option<&'a RapierContextEntityLink>),
30 &'a RigidBody,
31 Option<&'a GlobalTransform>,
32 Option<&'a Velocity>,
33 Option<&'a AdditionalMassProperties>,
34 Option<&'a ReadMassProperties>,
35 Option<&'a LockedAxes>,
36 Option<&'a ExternalForce>,
37 Option<&'a GravityScale>,
38 (Option<&'a Ccd>, Option<&'a SoftCcd>),
39 Option<&'a Dominance>,
40 Option<&'a Sleeping>,
41 Option<&'a Damping>,
42 Option<&'a RigidBodyDisabled>,
43 (
44 Option<&'a AdditionalSolverIterations>,
45 Option<&'a AdditionalPgsIterations>,
46 Option<&'a AllowFastRotation>,
47 ),
48);
49
50pub fn apply_rigid_body_user_changes(
52 (mut rigid_body_sets, mut context_simulations): (
53 Query<&mut RapierRigidBodySet>,
54 Query<&mut RapierContextSimulation>,
55 ),
56 config: Query<&RapierConfiguration>,
57 changed_rb_types: Query<
58 (&RapierRigidBodyHandle, &RapierContextEntityLink, &RigidBody),
59 Changed<RigidBody>,
60 >,
61 mut changed_transforms: Query<
62 (
63 &RapierRigidBodyHandle,
64 &RapierContextEntityLink,
65 &GlobalTransform,
66 Option<&mut TransformInterpolation>,
67 ),
68 Changed<GlobalTransform>,
69 >,
70 changed_velocities: Query<
71 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Velocity),
72 Changed<Velocity>,
73 >,
74 changed_additional_mass_props: Query<
75 (
76 Entity,
77 &RapierContextEntityLink,
78 &RapierRigidBodyHandle,
79 &AdditionalMassProperties,
80 ),
81 Changed<AdditionalMassProperties>,
82 >,
83 changed_locked_axes: Query<
84 (
85 &RapierRigidBodyHandle,
86 &RapierContextEntityLink,
87 &LockedAxes,
88 ),
89 Changed<LockedAxes>,
90 >,
91 changed_forces: Query<
92 (
93 &RapierRigidBodyHandle,
94 &RapierContextEntityLink,
95 &ExternalForce,
96 ),
97 Changed<ExternalForce>,
98 >,
99 mut changed_impulses: Query<
100 (
101 &RapierRigidBodyHandle,
102 &RapierContextEntityLink,
103 &mut ExternalImpulse,
104 ),
105 Changed<ExternalImpulse>,
106 >,
107 changed_gravity_scale: Query<
108 (
109 &RapierRigidBodyHandle,
110 &RapierContextEntityLink,
111 &GravityScale,
112 ),
113 Changed<GravityScale>,
114 >,
115 (changed_ccd, changed_soft_ccd): (
116 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &Ccd), Changed<Ccd>>,
117 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &SoftCcd), Changed<SoftCcd>>,
118 ),
119 (changed_dominance, changed_damping): (
120 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &Dominance), Changed<Dominance>>,
121 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &Damping), Changed<Damping>>,
122 ),
123 changed_sleeping: Query<
124 (&RapierRigidBodyHandle, &RapierContextEntityLink, &Sleeping),
125 Changed<Sleeping>,
126 >,
127 (
128 changed_disabled,
129 changed_additional_solver_iterations,
130 changed_additional_pgs_iterations,
131 changed_fast_rotation,
132 ): (
133 Query<
134 (
135 &RapierRigidBodyHandle,
136 &RapierContextEntityLink,
137 &RigidBodyDisabled,
138 ),
139 Changed<RigidBodyDisabled>,
140 >,
141 Query<
142 (
143 &RapierRigidBodyHandle,
144 &RapierContextEntityLink,
145 &AdditionalSolverIterations,
146 ),
147 Changed<AdditionalSolverIterations>,
148 >,
149 Query<
150 (
151 &RapierRigidBodyHandle,
152 &RapierContextEntityLink,
153 &AdditionalPgsIterations,
154 ),
155 Changed<AdditionalPgsIterations>,
156 >,
157 Query<(&RapierRigidBodyHandle, &RapierContextEntityLink), Changed<AllowFastRotation>>,
158 ),
159 #[cfg(feature = "dim3")] changed_gyroscopic_forces: Query<
160 (
161 &RapierRigidBodyHandle,
162 &RapierContextEntityLink,
163 &GyroscopicForces,
164 ),
165 Changed<GyroscopicForces>,
166 >,
167 mut mass_modified: MessageWriter<MassModifiedEvent>,
168) {
169 for (handle, link, sleeping) in changed_sleeping.iter() {
172 let rigidbody_set = rigid_body_sets
173 .get_mut(link.0)
174 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
175 .into_inner();
176
177 let mut wake_up_island = false;
178 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
179 let activation = rb.activation_mut();
180 activation.normalized_linear_threshold = sleeping.normalized_linear_threshold;
181 activation.angular_threshold = sleeping.angular_threshold;
182 activation.time_until_sleep = sleeping.time_until_sleep;
183
184 if !sleeping.sleeping && activation.sleeping {
185 rb.wake_up(true);
186 wake_up_island = true;
187 } else if sleeping.sleeping && !activation.sleeping {
188 rb.sleep();
189 }
190 }
191
192 if wake_up_island {
193 let mut context = context_simulations
195 .get_mut(link.0)
196 .expect(RAPIER_CONTEXT_EXPECT_ERROR);
197 context
198 .islands
199 .wake_up(&mut rigidbody_set.bodies, handle.0, true);
200 }
201 }
202
203 for (handle, link, rb_type) in changed_rb_types.iter() {
210 let context = rigid_body_sets
211 .get_mut(link.0)
212 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
213 .into_inner();
214 if let Some(rb) = context.bodies.get_mut(handle.0) {
215 if !rb.is_soft_frame() {
217 rb.set_body_type((*rb_type).into(), true);
218 }
219 }
220 }
221
222 let transform_changed_fn =
227 |handle: &RigidBodyHandle,
228 config: &RapierConfiguration,
229 transform: &GlobalTransform,
230 last_transform_set: &HashMap<RigidBodyHandle, GlobalTransform>| {
231 if config.force_update_from_transform_changes {
232 true
233 } else if let Some(prev) = last_transform_set.get(handle) {
234 *prev != *transform
235 } else {
236 true
237 }
238 };
239
240 for (handle, link, global_transform, mut interpolation) in changed_transforms.iter_mut() {
241 let rigidbody_set = rigid_body_sets
242 .get_mut(link.0)
243 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
244 .into_inner();
245 let config = config
246 .get(link.0)
247 .expect("Could not get `RapierConfiguration`");
248 let mut transform_changed = None;
250
251 if let Some(interpolation) = interpolation.as_deref_mut() {
252 transform_changed = transform_changed.or_else(|| {
253 Some(transform_changed_fn(
254 &handle.0,
255 config,
256 global_transform,
257 &rigidbody_set.last_body_transform_set,
258 ))
259 });
260
261 if transform_changed == Some(true) {
262 interpolation.start = None;
265 interpolation.end = None;
266 }
267 }
268 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
270 if rb.is_soft_frame() {
273 continue;
274 }
275 transform_changed = transform_changed.or_else(|| {
276 Some(transform_changed_fn(
277 &handle.0,
278 config,
279 global_transform,
280 &rigidbody_set.last_body_transform_set,
281 ))
282 });
283
284 match rb.body_type() {
285 RigidBodyType::KinematicPositionBased => {
286 if transform_changed == Some(true) {
287 rb.set_next_kinematic_position(utils::transform_to_iso(
288 &global_transform.compute_transform(),
289 ));
290 rigidbody_set
291 .last_body_transform_set
292 .insert(handle.0, *global_transform);
293 }
294 }
295 _ => {
296 if transform_changed == Some(true) {
297 rb.set_position(
298 utils::transform_to_iso(&global_transform.compute_transform()),
299 true,
300 );
301 rigidbody_set
302 .last_body_transform_set
303 .insert(handle.0, *global_transform);
304 }
305 }
306 }
307 }
308 }
309
310 for (handle, link, velocity) in changed_velocities.iter() {
311 let rigidbody_set = rigid_body_sets
312 .get_mut(link.0)
313 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
314 .into_inner();
315 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
316 if rb.is_soft_frame() {
318 continue;
319 }
320 rb.set_linvel(velocity.linear, true);
321 #[allow(clippy::useless_conversion)] rb.set_angvel(velocity.angular.into(), true);
323 }
324 }
325
326 for (entity, link, handle, mprops) in changed_additional_mass_props.iter() {
327 let rigidbody_set = rigid_body_sets
328 .get_mut(link.0)
329 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
330 .into_inner();
331 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
332 match mprops {
333 AdditionalMassProperties::MassProperties(mprops) => {
334 rb.set_additional_mass_properties(mprops.into_rapier(), true);
335 }
336 AdditionalMassProperties::Mass(mass) => {
337 rb.set_additional_mass(*mass, true);
338 }
339 }
340
341 mass_modified.write(entity.into());
342 }
343 }
344
345 for (handle, link, additional_solver_iters) in changed_additional_solver_iterations.iter() {
346 let rigidbody_set = rigid_body_sets
347 .get_mut(link.0)
348 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
349 .into_inner();
350 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
351 rb.set_additional_solver_iterations(additional_solver_iters.0);
352 }
353 }
354
355 for (handle, link, additional_pgs_iters) in changed_additional_pgs_iterations.iter() {
356 let rigidbody_set = rigid_body_sets
357 .get_mut(link.0)
358 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
359 .into_inner();
360 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
361 rb.set_additional_pgs_iterations(additional_pgs_iters.0);
362 }
363 }
364
365 for (handle, link) in changed_fast_rotation.iter() {
366 let rigidbody_set = rigid_body_sets
367 .get_mut(link.0)
368 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
369 .into_inner();
370 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
371 rb.set_allow_fast_rotation(true);
372 }
373 }
374
375 #[cfg(feature = "dim3")]
376 for (handle, link, gyroscopic_forces) in changed_gyroscopic_forces.iter() {
377 let rigidbody_set = rigid_body_sets
378 .get_mut(link.0)
379 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
380 .into_inner();
381 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
382 rb.enable_gyroscopic_forces(gyroscopic_forces.enabled);
383 }
384 }
385
386 for (handle, link, locked_axes) in changed_locked_axes.iter() {
387 let rigidbody_set = rigid_body_sets
388 .get_mut(link.0)
389 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
390 .into_inner();
391 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
392 rb.set_locked_axes((*locked_axes).into(), true);
393 }
394 }
395
396 for (handle, link, forces) in changed_forces.iter() {
397 let rigidbody_set = rigid_body_sets
398 .get_mut(link.0)
399 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
400 .into_inner();
401 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
402 rb.reset_forces(true);
403 rb.reset_torques(true);
404 rb.add_force(forces.force, true);
405 #[allow(clippy::useless_conversion)] rb.add_torque(forces.torque.into(), true);
407 }
408 }
409
410 for (handle, link, mut impulses) in changed_impulses.iter_mut() {
411 let rigidbody_set = rigid_body_sets
412 .get_mut(link.0)
413 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
414 .into_inner();
415 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
416 rb.apply_impulse(impulses.impulse, true);
417 #[allow(clippy::useless_conversion)] rb.apply_torque_impulse(impulses.torque_impulse.into(), true);
419 impulses.reset();
420 }
421 }
422
423 for (handle, link, gravity_scale) in changed_gravity_scale.iter() {
424 let rigidbody_set = rigid_body_sets
425 .get_mut(link.0)
426 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
427 .into_inner();
428 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
429 rb.set_gravity_scale(gravity_scale.0, true);
430 }
431 }
432
433 for (handle, link, ccd) in changed_ccd.iter() {
434 let rigidbody_set = rigid_body_sets
435 .get_mut(link.0)
436 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
437 .into_inner();
438 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
439 rb.enable_ccd(ccd.enabled);
440 }
441 }
442
443 for (handle, link, soft_ccd) in changed_soft_ccd.iter() {
444 let rigidbody_set = rigid_body_sets
445 .get_mut(link.0)
446 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
447 .into_inner();
448 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
449 rb.set_soft_ccd_prediction(soft_ccd.prediction);
450 }
451 }
452
453 for (handle, link, dominance) in changed_dominance.iter() {
454 let rigidbody_set = rigid_body_sets
455 .get_mut(link.0)
456 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
457 .into_inner();
458 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
459 rb.set_dominance_group(dominance.groups);
460 }
461 }
462
463 for (handle, link, damping) in changed_damping.iter() {
464 let rigidbody_set = rigid_body_sets
465 .get_mut(link.0)
466 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
467 .into_inner();
468 if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
469 rb.set_linear_damping(damping.linear_damping);
470 rb.set_angular_damping(damping.angular_damping);
471 }
472 }
473
474 for (handle, link, _) in changed_disabled.iter() {
475 let rigidbody_set = rigid_body_sets
476 .get_mut(link.0)
477 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
478 .into_inner();
479 if let Some(co) = rigidbody_set.bodies.get_mut(handle.0) {
480 co.set_enabled(false);
481 }
482 }
483}
484
485pub fn writeback_rigid_bodies(
488 mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
489 timestep_mode: Res<TimestepMode>,
490 config: Query<&RapierConfiguration>,
491 sim_to_render_time: Query<&SimulationToRenderTime>,
492 global_transforms: Query<&GlobalTransform>,
493 mut writeback: Query<
494 RigidBodyWritebackComponents,
495 (With<RigidBody>, Without<RigidBodyDisabled>),
496 >,
497) {
498 for (
499 handle,
500 link,
501 child_of,
502 transform,
503 mut interpolation,
504 mut velocity,
505 mut sleeping,
506 mut world_mass_props,
507 ) in writeback.iter_mut()
508 {
509 let config = config
510 .get(link.0)
511 .expect("Could not get `RapierConfiguration`");
512 if !config.physics_pipeline_active {
513 continue;
514 }
515 let handle = handle.0;
516
517 let rigid_body_set = rigid_body_sets
518 .get_mut(link.0)
519 .expect(RAPIER_CONTEXT_EXPECT_ERROR)
520 .into_inner();
521 let sim_to_render_time = sim_to_render_time
522 .get(link.0)
523 .expect("Could not get `SimulationToRenderTime`");
524 if let Some(rb) = rigid_body_set.bodies.get(handle) {
528 let mut interpolated_pos = utils::iso_to_transform(rb.position());
529
530 if let TimestepMode::Interpolated { dt, .. } = *timestep_mode {
531 if let Some(interpolation) = interpolation.as_deref_mut() {
532 if interpolation.end.is_none() {
533 interpolation.end = Some(*rb.position());
534 }
535
536 if let Some(interpolated) =
537 interpolation.lerp_slerp((dt + sim_to_render_time.diff) / dt)
538 {
539 interpolated_pos = utils::iso_to_transform(&interpolated);
540 }
541 }
542 }
543
544 if let Some(mut transform) = transform {
545 interpolated_pos = interpolated_pos.with_scale(transform.scale);
549
550 if let Some(parent_global_transform) =
560 child_of.and_then(|c| global_transforms.get(c.parent()).ok())
561 {
562 let (inverse_parent_scale, inverse_parent_rotation, inverse_parent_translation) =
566 parent_global_transform
567 .affine()
568 .inverse()
569 .to_scale_rotation_translation();
570 let new_rotation = inverse_parent_rotation * interpolated_pos.rotation;
571
572 #[allow(unused_mut)] let mut new_translation = inverse_parent_rotation
574 * inverse_parent_scale
575 * interpolated_pos.translation
576 + inverse_parent_translation;
577
578 #[cfg(feature = "dim2")]
580 {
581 new_translation.z = transform.translation.z;
582 }
583
584 if transform.rotation != new_rotation
585 || transform.translation != new_translation
586 {
587 transform.rotation = new_rotation;
591 transform.translation = new_translation;
592 }
593
594 let new_global_transform = parent_global_transform.mul_transform(*transform);
598
599 rigid_body_set
600 .last_body_transform_set
601 .insert(handle, new_global_transform);
602 } else {
603 #[cfg(feature = "dim2")]
605 {
606 interpolated_pos.translation.z = transform.translation.z;
607 }
608
609 if transform.rotation != interpolated_pos.rotation
610 || transform.translation != interpolated_pos.translation
611 {
612 transform.rotation = interpolated_pos.rotation;
616 transform.translation = interpolated_pos.translation;
617 }
618
619 rigid_body_set
620 .last_body_transform_set
621 .insert(handle, GlobalTransform::from(interpolated_pos));
622 }
623 }
624
625 if let Some(velocity) = &mut velocity {
626 let new_vel = Velocity {
627 linear: rb.linvel(),
628 #[cfg(feature = "dim3")]
629 angular: rb.angvel(),
630 #[cfg(feature = "dim2")]
631 angular: rb.angvel(),
632 };
633
634 if **velocity != new_vel {
638 **velocity = new_vel;
639 }
640 }
641
642 if let Some(sleeping) = &mut sleeping {
643 if sleeping.sleeping != rb.is_sleeping() {
647 sleeping.sleeping = rb.is_sleeping();
648 }
649 }
650
651 if let Some(world_mass_props) = &mut world_mass_props {
652 let new_props = ReadWorldMassProperties::from_rapier(rb.mass_properties());
653
654 if **world_mass_props != new_props {
658 **world_mass_props = new_props;
659 }
660 }
661 }
662 }
663}
664
665pub fn init_rigid_bodies(
667 mut commands: Commands,
668 context_links: RapierContextLinkResolver,
669 mut rigidbody_sets: Query<(Entity, &mut RapierRigidBodySet)>,
670 rigid_bodies: Query<RigidBodyComponents, Without<RapierRigidBodyHandle>>,
671 #[cfg(feature = "dim3")] gyroscopic_forces: Query<&GyroscopicForces>,
672) {
673 for (
674 (entity, entity_context_link),
675 rb,
676 transform,
677 vel,
678 additional_mass_props,
679 _mass_props,
680 locked_axes,
681 force,
682 gravity_scale,
683 (ccd, soft_ccd),
684 dominance,
685 sleep,
686 damping,
687 disabled,
688 (additional_solver_iters, additional_pgs_iters, fast_rotation),
689 ) in rigid_bodies.iter()
690 {
691 let mut builder = RigidBodyBuilder::new((*rb).into());
692 builder = builder.enabled(disabled.is_none());
693
694 if let Some(transform) = transform {
695 builder = builder.pose(utils::transform_to_iso(&transform.compute_transform()));
696 }
697
698 #[allow(clippy::useless_conversion)] if let Some(vel) = vel {
700 builder = builder.linvel(vel.linear.into()).angvel(vel.angular.into());
701 }
702
703 if let Some(locked_axes) = locked_axes {
704 builder = builder.locked_axes((*locked_axes).into())
705 }
706
707 if let Some(gravity_scale) = gravity_scale {
708 builder = builder.gravity_scale(gravity_scale.0);
709 }
710
711 if let Some(ccd) = ccd {
712 builder = builder.ccd_enabled(ccd.enabled)
713 }
714
715 if let Some(soft_ccd) = soft_ccd {
716 builder = builder.soft_ccd_prediction(soft_ccd.prediction)
717 }
718
719 if let Some(dominance) = dominance {
720 builder = builder.dominance_group(dominance.groups)
721 }
722
723 if let Some(sleep) = sleep {
724 builder = builder.sleeping(sleep.sleeping);
725 }
726
727 if let Some(damping) = damping {
728 builder = builder
729 .linear_damping(damping.linear_damping)
730 .angular_damping(damping.angular_damping);
731 }
732
733 if let Some(mprops) = additional_mass_props {
734 builder = match mprops {
735 AdditionalMassProperties::MassProperties(mprops) => {
736 builder.additional_mass_properties(mprops.into_rapier())
737 }
738 AdditionalMassProperties::Mass(mass) => builder.additional_mass(*mass),
739 };
740 }
741
742 if let Some(added_iters) = additional_solver_iters {
743 builder = builder.additional_solver_iterations(added_iters.0);
744 }
745
746 if let Some(added_iters) = additional_pgs_iters {
747 builder = builder.additional_pgs_iterations(added_iters.0);
748 }
749
750 if fast_rotation.is_some() {
751 builder = builder.allow_fast_rotation(true);
752 }
753
754 #[cfg(feature = "dim3")]
755 if let Ok(gyroscopic_forces) = gyroscopic_forces.get(entity) {
756 builder = builder.gyroscopic_forces_enabled(gyroscopic_forces.enabled);
757 }
758
759 builder = builder.user_data(entity.to_bits() as u128);
760
761 let mut rb = builder.build();
762
763 #[allow(clippy::useless_conversion)] if let Some(force) = force {
765 rb.add_force(force.force.into(), false);
766 rb.add_torque(force.torque.into(), false);
767 }
768
769 if let Some(sleep) = sleep {
774 let activation = rb.activation_mut();
775 activation.normalized_linear_threshold = sleep.normalized_linear_threshold;
776 activation.angular_threshold = sleep.angular_threshold;
777 activation.time_until_sleep = sleep.time_until_sleep;
778 }
779 let context_entity = context_links.resolve(entity, entity_context_link, &mut commands);
781 let Some(context_entity) = context_entity else {
782 continue;
783 };
784
785 let Ok((_, mut rigidbody_set)) = rigidbody_sets.get_mut(context_entity) else {
786 log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
787 continue;
788 };
789 let handle = rigidbody_set.bodies.insert(rb);
790 commands
791 .entity(entity)
792 .insert(RapierRigidBodyHandle(handle));
793 rigidbody_set.entity2body.insert(entity, handle);
794
795 if let Some(transform) = transform {
796 rigidbody_set
797 .last_body_transform_set
798 .insert(handle, *transform);
799 }
800 }
801}
802
803pub fn apply_initial_rigid_body_impulses(
809 mut context: Query<(&mut RapierRigidBodySet, &RapierContextColliders)>,
810 mut init_impulses: Query<
813 (Entity, &RapierContextEntityLink, &mut ExternalImpulse),
814 Without<RapierRigidBodyHandle>,
815 >,
816) {
817 for (entity, link, mut impulse) in init_impulses.iter_mut() {
818 let (mut rigidbody_set, context_colliders) =
819 context.get_mut(link.0).expect(RAPIER_CONTEXT_EXPECT_ERROR);
820 let rigidbody_set = &mut *rigidbody_set;
821
822 let bodies = &mut rigidbody_set.bodies;
823 if let Some(rb) = rigidbody_set
824 .entity2body
825 .get(&entity)
826 .and_then(|h| bodies.get_mut(*h))
827 {
828 rb.recompute_mass_properties_from_colliders(&context_colliders.colliders);
830 rb.apply_impulse(impulse.impulse, false);
832
833 #[allow(clippy::useless_conversion)] rb.apply_torque_impulse(impulse.torque_impulse.into(), false);
835
836 impulse.reset();
837 }
838 }
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844 use crate::plugin::{NoUserData, RapierPhysicsPlugin};
845 use bevy::time::{TimePlugin, TimeUpdateStrategy};
846 use rapier::dynamics::LockedAxes as RapierLockedAxes;
847
848 fn test_app() -> App {
849 let mut app = App::new();
850 app.add_plugins((
851 TransformPlugin,
852 TimePlugin,
853 RapierPhysicsPlugin::<NoUserData>::default(),
854 ))
855 .insert_resource(TimeUpdateStrategy::ManualDuration(
856 std::time::Duration::from_secs_f32(1.0 / 60.0),
857 ));
858 app.finish();
859 app
860 }
861
862 fn with_body<R>(
863 app: &mut App,
864 entity: Entity,
865 f: impl FnOnce(&RapierRigidBodySet, &rapier::dynamics::RigidBody) -> R,
866 ) -> R {
867 let world = app.world_mut();
868 let set = world.query::<&RapierRigidBodySet>().single(world).unwrap();
869 let rb = set.bodies.get(set.entity2body()[&entity]).unwrap();
870 f(set, rb)
871 }
872
873 #[test]
874 fn rigid_body_components_are_reset_on_removal() {
875 let mut app = test_app();
876 app.update();
877
878 let entity = app
879 .world_mut()
880 .spawn((
881 Transform::default(),
882 RigidBody::Dynamic,
883 ExternalForce {
884 force: Vect::X,
885 ..default()
886 },
887 Damping {
888 linear_damping: 1.0,
889 angular_damping: 2.0,
890 },
891 GravityScale(3.0),
892 (Ccd::enabled(), SoftCcd { prediction: 4.0 }),
893 Dominance::group(5),
894 LockedAxes::ROTATION_LOCKED,
895 AdditionalMassProperties::Mass(6.0),
896 (AdditionalSolverIterations(7), AdditionalPgsIterations(8)),
897 AllowFastRotation,
898 Sleeping {
899 time_until_sleep: 9.0,
900 ..Sleeping::disabled()
901 },
902 ))
903 .id();
904 #[cfg(feature = "dim3")]
905 app.world_mut()
906 .entity_mut(entity)
907 .insert(GyroscopicForces::disabled());
908 app.update();
909
910 with_body(&mut app, entity, |_, rb| {
911 assert_eq!(rb.user_force(), Vect::X);
912 assert_eq!(rb.linear_damping(), 1.0);
913 assert_eq!(rb.angular_damping(), 2.0);
914 assert_eq!(rb.gravity_scale(), 3.0);
915 assert!(rb.is_ccd_enabled());
916 assert_eq!(rb.soft_ccd_prediction(), 4.0);
917 assert_eq!(rb.dominance_group(), 5);
918 assert_eq!(rb.locked_axes(), RapierLockedAxes::ROTATION_LOCKED);
919 assert!(rb.mass_properties().additional_local_mprops.is_some());
920 assert_eq!(rb.additional_solver_iterations(), 7);
921 assert_eq!(rb.additional_pgs_iterations(), 8);
922 assert!(rb.is_fast_rotation_allowed());
923 assert_eq!(rb.activation().normalized_linear_threshold, -1.0);
924 assert_eq!(rb.activation().time_until_sleep, 9.0);
925 #[cfg(feature = "dim3")]
926 assert!(!rb.gyroscopic_forces_enabled());
927 });
928
929 app.world_mut().entity_mut(entity).remove::<(
930 ExternalForce,
931 Damping,
932 GravityScale,
933 Ccd,
934 SoftCcd,
935 Dominance,
936 LockedAxes,
937 AdditionalMassProperties,
938 AdditionalSolverIterations,
939 AdditionalPgsIterations,
940 AllowFastRotation,
941 Sleeping,
942 )>();
943 #[cfg(feature = "dim3")]
944 app.world_mut()
945 .entity_mut(entity)
946 .remove::<GyroscopicForces>();
947 app.update();
948
949 let default_sleeping = Sleeping::default();
950 with_body(&mut app, entity, |_, rb| {
951 assert_eq!(rb.user_force(), Vect::ZERO);
952 assert_eq!(rb.linear_damping(), 0.0);
953 assert_eq!(rb.angular_damping(), 0.0);
954 assert_eq!(rb.gravity_scale(), 1.0);
955 assert!(!rb.is_ccd_enabled());
956 assert_eq!(rb.soft_ccd_prediction(), 0.0);
957 assert_eq!(rb.dominance_group(), 0);
958 assert_eq!(rb.locked_axes(), RapierLockedAxes::empty());
959 assert_eq!(rb.mass(), 0.0);
960 assert_eq!(rb.additional_solver_iterations(), 0);
961 assert_eq!(rb.additional_pgs_iterations(), 0);
962 assert!(!rb.is_fast_rotation_allowed());
963 assert_eq!(
964 rb.activation().normalized_linear_threshold,
965 default_sleeping.normalized_linear_threshold
966 );
967 assert_eq!(
968 rb.activation().angular_threshold,
969 default_sleeping.angular_threshold
970 );
971 assert_eq!(
972 rb.activation().time_until_sleep,
973 default_sleeping.time_until_sleep
974 );
975 #[cfg(feature = "dim3")]
976 assert!(rb.gyroscopic_forces_enabled());
977 });
978 }
979
980 #[test]
981 fn component_reinserted_in_same_frame_is_not_reset() {
982 let mut app = test_app();
983 app.update();
984
985 let entity = app
986 .world_mut()
987 .spawn((Transform::default(), RigidBody::Dynamic, GravityScale(2.0)))
988 .id();
989 app.update();
990
991 app.world_mut()
992 .entity_mut(entity)
993 .remove::<GravityScale>()
994 .insert(GravityScale(3.0));
995 app.update();
996
997 with_body(&mut app, entity, |_, rb| {
998 assert_eq!(rb.gravity_scale(), 3.0)
999 });
1000 }
1001
1002 #[test]
1003 fn world_mass_properties_are_written_back() {
1004 let mut app = test_app();
1005 app.update();
1006
1007 let translation = Vec3::new(1.0, 2.0, 0.0);
1008 let entity = app
1009 .world_mut()
1010 .spawn((
1011 Transform::from_translation(translation),
1012 RigidBody::Dynamic,
1013 Collider::ball(0.5),
1014 LockedAxes::TRANSLATION_LOCKED_X,
1015 GravityScale(0.0),
1016 ReadWorldMassProperties::default(),
1017 ))
1018 .id();
1019 app.update();
1020
1021 let props = *app.world().get::<ReadWorldMassProperties>(entity).unwrap();
1022 #[cfg(feature = "dim2")]
1023 let expected_com = translation.truncate();
1024 #[cfg(feature = "dim3")]
1025 let expected_com = translation;
1026 approx::assert_relative_eq!(props.center_of_mass, expected_com, epsilon = 1.0e-5);
1027 assert_eq!(props.effective_inv_mass.x, 0.0);
1028 assert!(props.effective_inv_mass.y > 0.0);
1029
1030 with_body(&mut app, entity, |set, rb| {
1031 assert_eq!(set.center_of_mass(entity), Some(props.center_of_mass));
1032 assert_eq!(set.mass(entity), Some(rb.mass()));
1033 assert_eq!(set.is_moving(entity), Some(false));
1034 assert_eq!(set.effective_dominance_group(entity), Some(0));
1035 assert_eq!(set.center_of_mass(Entity::PLACEHOLDER), None);
1036 });
1037 }
1038
1039 #[test]
1040 fn waking_up_a_body_wakes_up_its_island() {
1041 let mut app = test_app();
1042 app.update();
1043
1044 let spawn_body = |app: &mut App, x: f32| {
1045 app.world_mut()
1046 .spawn((
1047 Transform::from_xyz(x, 0.0, 0.0),
1048 RigidBody::Dynamic,
1049 Collider::ball(0.5),
1050 GravityScale(0.0),
1051 Sleeping::default(),
1052 ))
1053 .id()
1054 };
1055 let body1 = spawn_body(&mut app, 0.0);
1056 let body2 = spawn_body(&mut app, 2.0);
1057 app.world_mut().entity_mut(body2).insert(ImpulseJoint::new(
1058 body1,
1059 FixedJointBuilder::new().local_anchor1(Vect::X * 2.0),
1060 ));
1061
1062 for _ in 0..120 {
1063 app.update();
1064 }
1065 assert!(app.world().get::<Sleeping>(body1).unwrap().sleeping);
1066 assert!(app.world().get::<Sleeping>(body2).unwrap().sleeping);
1067
1068 app.world_mut().get_mut::<Sleeping>(body2).unwrap().sleeping = false;
1069 app.update();
1070
1071 with_body(&mut app, body1, |_, rb| assert!(!rb.is_sleeping()));
1072 with_body(&mut app, body2, |_, rb| assert!(!rb.is_sleeping()));
1073 }
1074}