Skip to main content

bevy_rapier3d/plugin/systems/
rigid_body.rs

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
15/// Components that will be updated after a physics step.
16pub 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
27/// Components related to rigid-bodies.
28pub 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
50/// System responsible for applying changes the user made to a rigid-body-related component.
51pub 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    // Deal with sleeping first, because other changes may then wake-up the
170    // rigid-body again.
171    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            // Wake up the whole island, not only this body.
194            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    // NOTE: we must change the rigid-body type before updating the
204    //       transform or velocity. Otherwise, if the rigid-body was fixed
205    //       and changed to anything else, the velocity change wouldn’t have any effect.
206    //       Similarly, if the rigid-body was kinematic position-based before and
207    //       changed to anything else, a transform change would modify the next
208    //       position instead of the current one.
209    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            // The type of a soft-body cluster proxy is managed by Rapier.
216            if !rb.is_soft_frame() {
217                rb.set_body_type((*rb_type).into(), true);
218            }
219        }
220    }
221
222    // Manually checks if the transform changed.
223    // This is needed for detecting if the user actually changed the rigid-body
224    // transform, or if it was just the change we made in our `writeback_rigid_bodies`
225    // system.
226    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        // Use an Option<bool> to avoid running the check twice.
249        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                // Reset the interpolation so we don’t overwrite
263                // the user’s input.
264                interpolation.start = None;
265                interpolation.end = None;
266            }
267        }
268        // TODO: avoid to run multiple times the mutable deref ?
269        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
270            // The pose of a soft-body cluster proxy follows its particles: it is written back
271            // to the transform, but never read from it.
272            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            // The velocity of a soft-body cluster proxy follows its particles.
317            if rb.is_soft_frame() {
318                continue;
319            }
320            rb.set_linvel(velocity.linear, true);
321            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
322            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)] // Need to convert if dim3 enabled
406            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)] // Need to convert if dim3 enabled
418            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
485/// System responsible for writing the result of the last simulation step into our `bevy_rapier`
486/// components and the [`GlobalTransform`] component.
487pub 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        // TODO: do this the other way round: iterate through Rapier’s RigidBodySet on the active bodies,
525        // and update the components accordingly. That way, we don’t have to iterate through the entities that weren’t changed
526        // by physics (for example because they are sleeping).
527        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                // NOTE: Rapier's `RigidBody` doesn't know its own scale as it is encoded
546                //       directly within its collider, so we have to retrieve it from
547                //       the scale of its bevy transform.
548                interpolated_pos = interpolated_pos.with_scale(transform.scale);
549
550                // NOTE: we query the parent’s global transform here, which is a bit
551                //       unfortunate (performance-wise). An alternative would be to
552                //       deduce the parent’s global transform from the current entity’s
553                //       global transform. However, this makes it nearly impossible
554                //       (because of rounding errors) to predict the exact next value this
555                //       entity’s global transform will get after the next transform
556                //       propagation, which breaks our transform modification detection
557                //       that we do to detect if the user’s transform has to be written
558                //       into the rigid-body.
559                if let Some(parent_global_transform) =
560                    child_of.and_then(|c| global_transforms.get(c.parent()).ok())
561                {
562                    // We need to compute the new local transform such that:
563                    // curr_parent_global_transform * new_transform = interpolated_pos
564                    // new_transform = curr_parent_global_transform.inverse() * interpolated_pos
565                    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)] // mut is needed in 2D but not in 3D.
573                    let mut new_translation = inverse_parent_rotation
574                        * inverse_parent_scale
575                        * interpolated_pos.translation
576                        + inverse_parent_translation;
577
578                    // In 2D, preserve the transform `z` component that may have been set by the user
579                    #[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                        // NOTE: we write the new value only if there was an
588                        //       actual change, in order to not trigger bevy’s
589                        //       change tracking when the values didn’t change.
590                        transform.rotation = new_rotation;
591                        transform.translation = new_translation;
592                    }
593
594                    // NOTE: we need to compute the result of the next transform propagation
595                    //       to make sure that our change detection for transforms is exact
596                    //       despite rounding errors.
597                    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                    // In 2D, preserve the transform `z` component that may have been set by the user
604                    #[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                        // NOTE: we write the new value only if there was an
613                        //       actual change, in order to not trigger bevy’s
614                        //       change tracking when the values didn’t change.
615                        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                // NOTE: we write the new value only if there was an
635                //       actual change, in order to not trigger bevy’s
636                //       change tracking when the values didn’t change.
637                if **velocity != new_vel {
638                    **velocity = new_vel;
639                }
640            }
641
642            if let Some(sleeping) = &mut sleeping {
643                // NOTE: we write the new value only if there was an
644                //       actual change, in order to not trigger bevy’s
645                //       change tracking when the values didn’t change.
646                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                // NOTE: we write the new value only if there was an
655                //       actual change, in order to not trigger bevy’s
656                //       change tracking when the values didn’t change.
657                if **world_mass_props != new_props {
658                    **world_mass_props = new_props;
659                }
660            }
661        }
662    }
663}
664
665/// System responsible for creating new Rapier rigid-bodies from the related `bevy_rapier` components.
666pub 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)] // Need to convert if dim3 enabled
699        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)] // Need to convert if dim3 enabled
764        if let Some(force) = force {
765            rb.add_force(force.force.into(), false);
766            rb.add_torque(force.torque.into(), false);
767        }
768
769        // NOTE: we can’t apply impulses yet at this point because
770        //       the rigid-body’s mass isn’t up-to-date yet (its
771        //       attached colliders, if any, haven’t been created yet).
772
773        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        // Use the RapierContextEntityLink, or insert the context of an ancestor or the default one.
780        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
803/// This applies the initial impulse given to a rigid-body when it is created.
804///
805/// This cannot be done inside `init_rigid_bodies` because impulses require the rigid-body
806/// mass to be available, which it was not because colliders were not created yet. As a
807/// result, we run this system after the collider creation.
808pub fn apply_initial_rigid_body_impulses(
809    mut context: Query<(&mut RapierRigidBodySet, &RapierContextColliders)>,
810    // We can’t use RapierRigidBodyHandle yet because its creation command hasn’t been
811    // executed yet.
812    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            // Make sure the mass-properties are computed.
829            rb.recompute_mass_properties_from_colliders(&context_colliders.colliders);
830            // Apply the impulse.
831            rb.apply_impulse(impulse.impulse, false);
832
833            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
834            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}