bevy_rapier2d/plugin/systems/
rigid_body.rs

1use crate::dynamics::RapierRigidBodyHandle;
2use crate::plugin::context::systemparams::RAPIER_CONTEXT_EXPECT_ERROR;
3use crate::plugin::context::{
4    DefaultRapierContext, RapierContextColliders, RapierContextEntityLink, RapierRigidBodySet,
5};
6use crate::plugin::{configuration::TimestepMode, RapierConfiguration};
7use crate::{dynamics::RigidBody, plugin::context::SimulationToRenderTime};
8use crate::{prelude::*, utils};
9use bevy::prelude::*;
10use rapier::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
11use std::collections::HashMap;
12
13/// Components that will be updated after a physics step.
14pub type RigidBodyWritebackComponents<'a> = (
15    &'a RapierRigidBodyHandle,
16    &'a RapierContextEntityLink,
17    Option<&'a ChildOf>,
18    Option<&'a mut Transform>,
19    Option<&'a mut TransformInterpolation>,
20    Option<&'a mut Velocity>,
21    Option<&'a mut Sleeping>,
22);
23
24/// Components related to rigid-bodies.
25pub type RigidBodyComponents<'a> = (
26    (Entity, Option<&'a RapierContextEntityLink>),
27    &'a RigidBody,
28    Option<&'a GlobalTransform>,
29    Option<&'a Velocity>,
30    Option<&'a AdditionalMassProperties>,
31    Option<&'a ReadMassProperties>,
32    Option<&'a LockedAxes>,
33    Option<&'a ExternalForce>,
34    Option<&'a GravityScale>,
35    (Option<&'a Ccd>, Option<&'a SoftCcd>),
36    Option<&'a Dominance>,
37    Option<&'a Sleeping>,
38    Option<&'a Damping>,
39    Option<&'a RigidBodyDisabled>,
40    Option<&'a AdditionalSolverIterations>,
41);
42
43/// System responsible for applying changes the user made to a rigid-body-related component.
44pub fn apply_rigid_body_user_changes(
45    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
46    config: Query<&RapierConfiguration>,
47    changed_rb_types: Query<
48        (&RapierRigidBodyHandle, &RapierContextEntityLink, &RigidBody),
49        Changed<RigidBody>,
50    >,
51    mut changed_transforms: Query<
52        (
53            &RapierRigidBodyHandle,
54            &RapierContextEntityLink,
55            &GlobalTransform,
56            Option<&mut TransformInterpolation>,
57        ),
58        Changed<GlobalTransform>,
59    >,
60    changed_velocities: Query<
61        (&RapierRigidBodyHandle, &RapierContextEntityLink, &Velocity),
62        Changed<Velocity>,
63    >,
64    changed_additional_mass_props: Query<
65        (
66            Entity,
67            &RapierContextEntityLink,
68            &RapierRigidBodyHandle,
69            &AdditionalMassProperties,
70        ),
71        Changed<AdditionalMassProperties>,
72    >,
73    changed_locked_axes: Query<
74        (
75            &RapierRigidBodyHandle,
76            &RapierContextEntityLink,
77            &LockedAxes,
78        ),
79        Changed<LockedAxes>,
80    >,
81    changed_forces: Query<
82        (
83            &RapierRigidBodyHandle,
84            &RapierContextEntityLink,
85            &ExternalForce,
86        ),
87        Changed<ExternalForce>,
88    >,
89    mut changed_impulses: Query<
90        (
91            &RapierRigidBodyHandle,
92            &RapierContextEntityLink,
93            &mut ExternalImpulse,
94        ),
95        Changed<ExternalImpulse>,
96    >,
97    changed_gravity_scale: Query<
98        (
99            &RapierRigidBodyHandle,
100            &RapierContextEntityLink,
101            &GravityScale,
102        ),
103        Changed<GravityScale>,
104    >,
105    (changed_ccd, changed_soft_ccd): (
106        Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &Ccd), Changed<Ccd>>,
107        Query<(&RapierRigidBodyHandle, &RapierContextEntityLink, &SoftCcd), Changed<SoftCcd>>,
108    ),
109    changed_dominance: Query<
110        (&RapierRigidBodyHandle, &RapierContextEntityLink, &Dominance),
111        Changed<Dominance>,
112    >,
113    changed_sleeping: Query<
114        (&RapierRigidBodyHandle, &RapierContextEntityLink, &Sleeping),
115        Changed<Sleeping>,
116    >,
117    changed_damping: Query<
118        (&RapierRigidBodyHandle, &RapierContextEntityLink, &Damping),
119        Changed<Damping>,
120    >,
121    (changed_disabled, changed_additional_solver_iterations): (
122        Query<
123            (
124                &RapierRigidBodyHandle,
125                &RapierContextEntityLink,
126                &RigidBodyDisabled,
127            ),
128            Changed<RigidBodyDisabled>,
129        >,
130        Query<
131            (
132                &RapierRigidBodyHandle,
133                &RapierContextEntityLink,
134                &AdditionalSolverIterations,
135            ),
136            Changed<AdditionalSolverIterations>,
137        >,
138    ),
139    mut mass_modified: MessageWriter<MassModifiedEvent>,
140) {
141    // Deal with sleeping first, because other changes may then wake-up the
142    // rigid-body again.
143    for (handle, link, sleeping) in changed_sleeping.iter() {
144        let rigidbody_set = rigid_body_sets
145            .get_mut(link.0)
146            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
147            .into_inner();
148
149        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
150            let activation = rb.activation_mut();
151            activation.normalized_linear_threshold = sleeping.normalized_linear_threshold;
152            activation.angular_threshold = sleeping.angular_threshold;
153
154            if !sleeping.sleeping && activation.sleeping {
155                rb.wake_up(true);
156            } else if sleeping.sleeping && !activation.sleeping {
157                rb.sleep();
158            }
159        }
160    }
161
162    // NOTE: we must change the rigid-body type before updating the
163    //       transform or velocity. Otherwise, if the rigid-body was fixed
164    //       and changed to anything else, the velocity change wouldn’t have any effect.
165    //       Similarly, if the rigid-body was kinematic position-based before and
166    //       changed to anything else, a transform change would modify the next
167    //       position instead of the current one.
168    for (handle, link, rb_type) in changed_rb_types.iter() {
169        let context = rigid_body_sets
170            .get_mut(link.0)
171            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
172            .into_inner();
173        if let Some(rb) = context.bodies.get_mut(handle.0) {
174            rb.set_body_type((*rb_type).into(), true);
175        }
176    }
177
178    // Manually checks if the transform changed.
179    // This is needed for detecting if the user actually changed the rigid-body
180    // transform, or if it was just the change we made in our `writeback_rigid_bodies`
181    // system.
182    let transform_changed_fn =
183        |handle: &RigidBodyHandle,
184         config: &RapierConfiguration,
185         transform: &GlobalTransform,
186         last_transform_set: &HashMap<RigidBodyHandle, GlobalTransform>| {
187            if config.force_update_from_transform_changes {
188                true
189            } else if let Some(prev) = last_transform_set.get(handle) {
190                *prev != *transform
191            } else {
192                true
193            }
194        };
195
196    for (handle, link, global_transform, mut interpolation) in changed_transforms.iter_mut() {
197        let rigidbody_set = rigid_body_sets
198            .get_mut(link.0)
199            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
200            .into_inner();
201        let config = config
202            .get(link.0)
203            .expect("Could not get `RapierConfiguration`");
204        // Use an Option<bool> to avoid running the check twice.
205        let mut transform_changed = None;
206
207        if let Some(interpolation) = interpolation.as_deref_mut() {
208            transform_changed = transform_changed.or_else(|| {
209                Some(transform_changed_fn(
210                    &handle.0,
211                    config,
212                    global_transform,
213                    &rigidbody_set.last_body_transform_set,
214                ))
215            });
216
217            if transform_changed == Some(true) {
218                // Reset the interpolation so we don’t overwrite
219                // the user’s input.
220                interpolation.start = None;
221                interpolation.end = None;
222            }
223        }
224        // TODO: avoid to run multiple times the mutable deref ?
225        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
226            transform_changed = transform_changed.or_else(|| {
227                Some(transform_changed_fn(
228                    &handle.0,
229                    config,
230                    global_transform,
231                    &rigidbody_set.last_body_transform_set,
232                ))
233            });
234
235            match rb.body_type() {
236                RigidBodyType::KinematicPositionBased => {
237                    if transform_changed == Some(true) {
238                        rb.set_next_kinematic_position(utils::transform_to_iso(
239                            &global_transform.compute_transform(),
240                        ));
241                        rigidbody_set
242                            .last_body_transform_set
243                            .insert(handle.0, *global_transform);
244                    }
245                }
246                _ => {
247                    if transform_changed == Some(true) {
248                        rb.set_position(
249                            utils::transform_to_iso(&global_transform.compute_transform()),
250                            true,
251                        );
252                        rigidbody_set
253                            .last_body_transform_set
254                            .insert(handle.0, *global_transform);
255                    }
256                }
257            }
258        }
259    }
260
261    for (handle, link, velocity) in changed_velocities.iter() {
262        let rigidbody_set = rigid_body_sets
263            .get_mut(link.0)
264            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
265            .into_inner();
266        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
267            rb.set_linvel(velocity.linvel.into(), true);
268            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
269            rb.set_angvel(velocity.angvel.into(), true);
270        }
271    }
272
273    for (entity, link, handle, mprops) in changed_additional_mass_props.iter() {
274        let rigidbody_set = rigid_body_sets
275            .get_mut(link.0)
276            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
277            .into_inner();
278        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
279            match mprops {
280                AdditionalMassProperties::MassProperties(mprops) => {
281                    rb.set_additional_mass_properties(mprops.into_rapier(), true);
282                }
283                AdditionalMassProperties::Mass(mass) => {
284                    rb.set_additional_mass(*mass, true);
285                }
286            }
287
288            mass_modified.write(entity.into());
289        }
290    }
291
292    for (handle, link, additional_solver_iters) in changed_additional_solver_iterations.iter() {
293        let rigidbody_set = rigid_body_sets
294            .get_mut(link.0)
295            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
296            .into_inner();
297        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
298            rb.set_additional_solver_iterations(additional_solver_iters.0);
299        }
300    }
301
302    for (handle, link, locked_axes) in changed_locked_axes.iter() {
303        let rigidbody_set = rigid_body_sets
304            .get_mut(link.0)
305            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
306            .into_inner();
307        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
308            rb.set_locked_axes((*locked_axes).into(), true);
309        }
310    }
311
312    for (handle, link, forces) in changed_forces.iter() {
313        let rigidbody_set = rigid_body_sets
314            .get_mut(link.0)
315            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
316            .into_inner();
317        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
318            rb.reset_forces(true);
319            rb.reset_torques(true);
320            rb.add_force(forces.force.into(), true);
321            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
322            rb.add_torque(forces.torque.into(), true);
323        }
324    }
325
326    for (handle, link, mut impulses) in changed_impulses.iter_mut() {
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            rb.apply_impulse(impulses.impulse.into(), true);
333            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
334            rb.apply_torque_impulse(impulses.torque_impulse.into(), true);
335            impulses.reset();
336        }
337    }
338
339    for (handle, link, gravity_scale) in changed_gravity_scale.iter() {
340        let rigidbody_set = rigid_body_sets
341            .get_mut(link.0)
342            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
343            .into_inner();
344        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
345            rb.set_gravity_scale(gravity_scale.0, true);
346        }
347    }
348
349    for (handle, link, ccd) in changed_ccd.iter() {
350        let rigidbody_set = rigid_body_sets
351            .get_mut(link.0)
352            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
353            .into_inner();
354        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
355            rb.enable_ccd(ccd.enabled);
356        }
357    }
358
359    for (handle, link, soft_ccd) in changed_soft_ccd.iter() {
360        let rigidbody_set = rigid_body_sets
361            .get_mut(link.0)
362            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
363            .into_inner();
364        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
365            rb.set_soft_ccd_prediction(soft_ccd.prediction);
366        }
367    }
368
369    for (handle, link, dominance) in changed_dominance.iter() {
370        let rigidbody_set = rigid_body_sets
371            .get_mut(link.0)
372            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
373            .into_inner();
374        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
375            rb.set_dominance_group(dominance.groups);
376        }
377    }
378
379    for (handle, link, damping) in changed_damping.iter() {
380        let rigidbody_set = rigid_body_sets
381            .get_mut(link.0)
382            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
383            .into_inner();
384        if let Some(rb) = rigidbody_set.bodies.get_mut(handle.0) {
385            rb.set_linear_damping(damping.linear_damping);
386            rb.set_angular_damping(damping.angular_damping);
387        }
388    }
389
390    for (handle, link, _) in changed_disabled.iter() {
391        let rigidbody_set = rigid_body_sets
392            .get_mut(link.0)
393            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
394            .into_inner();
395        if let Some(co) = rigidbody_set.bodies.get_mut(handle.0) {
396            co.set_enabled(false);
397        }
398    }
399}
400
401/// System responsible for writing the result of the last simulation step into our `bevy_rapier`
402/// components and the [`GlobalTransform`] component.
403pub fn writeback_rigid_bodies(
404    mut rigid_body_sets: Query<&mut RapierRigidBodySet>,
405    timestep_mode: Res<TimestepMode>,
406    config: Query<&RapierConfiguration>,
407    sim_to_render_time: Query<&SimulationToRenderTime>,
408    global_transforms: Query<&GlobalTransform>,
409    mut writeback: Query<
410        RigidBodyWritebackComponents,
411        (With<RigidBody>, Without<RigidBodyDisabled>),
412    >,
413) {
414    for (handle, link, child_of, transform, mut interpolation, mut velocity, mut sleeping) in
415        writeback.iter_mut()
416    {
417        let config = config
418            .get(link.0)
419            .expect("Could not get `RapierConfiguration`");
420        if !config.physics_pipeline_active {
421            continue;
422        }
423        let handle = handle.0;
424
425        let rigid_body_set = rigid_body_sets
426            .get_mut(link.0)
427            .expect(RAPIER_CONTEXT_EXPECT_ERROR)
428            .into_inner();
429        let sim_to_render_time = sim_to_render_time
430            .get(link.0)
431            .expect("Could not get `SimulationToRenderTime`");
432        // TODO: do this the other way round: iterate through Rapier’s RigidBodySet on the active bodies,
433        // and update the components accordingly. That way, we don’t have to iterate through the entities that weren’t changed
434        // by physics (for example because they are sleeping).
435        if let Some(rb) = rigid_body_set.bodies.get(handle) {
436            let mut interpolated_pos = utils::iso_to_transform(rb.position());
437
438            if let TimestepMode::Interpolated { dt, .. } = *timestep_mode {
439                if let Some(interpolation) = interpolation.as_deref_mut() {
440                    if interpolation.end.is_none() {
441                        interpolation.end = Some(*rb.position());
442                    }
443
444                    if let Some(interpolated) =
445                        interpolation.lerp_slerp((dt + sim_to_render_time.diff) / dt)
446                    {
447                        interpolated_pos = utils::iso_to_transform(&interpolated);
448                    }
449                }
450            }
451
452            if let Some(mut transform) = transform {
453                // NOTE: Rapier's `RigidBody` doesn't know its own scale as it is encoded
454                //       directly within its collider, so we have to retrieve it from
455                //       the scale of its bevy transform.
456                interpolated_pos = interpolated_pos.with_scale(transform.scale);
457
458                // NOTE: we query the parent’s global transform here, which is a bit
459                //       unfortunate (performance-wise). An alternative would be to
460                //       deduce the parent’s global transform from the current entity’s
461                //       global transform. However, this makes it nearly impossible
462                //       (because of rounding errors) to predict the exact next value this
463                //       entity’s global transform will get after the next transform
464                //       propagation, which breaks our transform modification detection
465                //       that we do to detect if the user’s transform has to be written
466                //       into the rigid-body.
467                if let Some(parent_global_transform) =
468                    child_of.and_then(|c| global_transforms.get(c.parent()).ok())
469                {
470                    // We need to compute the new local transform such that:
471                    // curr_parent_global_transform * new_transform = interpolated_pos
472                    // new_transform = curr_parent_global_transform.inverse() * interpolated_pos
473                    let (inverse_parent_scale, inverse_parent_rotation, inverse_parent_translation) =
474                        parent_global_transform
475                            .affine()
476                            .inverse()
477                            .to_scale_rotation_translation();
478                    let new_rotation = inverse_parent_rotation * interpolated_pos.rotation;
479
480                    #[allow(unused_mut)] // mut is needed in 2D but not in 3D.
481                    let mut new_translation = inverse_parent_rotation
482                        * inverse_parent_scale
483                        * interpolated_pos.translation
484                        + inverse_parent_translation;
485
486                    // In 2D, preserve the transform `z` component that may have been set by the user
487                    #[cfg(feature = "dim2")]
488                    {
489                        new_translation.z = transform.translation.z;
490                    }
491
492                    if transform.rotation != new_rotation
493                        || transform.translation != new_translation
494                    {
495                        // NOTE: we write the new value only if there was an
496                        //       actual change, in order to not trigger bevy’s
497                        //       change tracking when the values didn’t change.
498                        transform.rotation = new_rotation;
499                        transform.translation = new_translation;
500                    }
501
502                    // NOTE: we need to compute the result of the next transform propagation
503                    //       to make sure that our change detection for transforms is exact
504                    //       despite rounding errors.
505                    let new_global_transform = parent_global_transform.mul_transform(*transform);
506
507                    rigid_body_set
508                        .last_body_transform_set
509                        .insert(handle, new_global_transform);
510                } else {
511                    // In 2D, preserve the transform `z` component that may have been set by the user
512                    #[cfg(feature = "dim2")]
513                    {
514                        interpolated_pos.translation.z = transform.translation.z;
515                    }
516
517                    if transform.rotation != interpolated_pos.rotation
518                        || transform.translation != interpolated_pos.translation
519                    {
520                        // NOTE: we write the new value only if there was an
521                        //       actual change, in order to not trigger bevy’s
522                        //       change tracking when the values didn’t change.
523                        transform.rotation = interpolated_pos.rotation;
524                        transform.translation = interpolated_pos.translation;
525                    }
526
527                    rigid_body_set
528                        .last_body_transform_set
529                        .insert(handle, GlobalTransform::from(interpolated_pos));
530                }
531            }
532
533            if let Some(velocity) = &mut velocity {
534                let new_vel = Velocity {
535                    linvel: (*rb.linvel()).into(),
536                    #[cfg(feature = "dim3")]
537                    angvel: (*rb.angvel()).into(),
538                    #[cfg(feature = "dim2")]
539                    angvel: rb.angvel(),
540                };
541
542                // NOTE: we write the new value only if there was an
543                //       actual change, in order to not trigger bevy’s
544                //       change tracking when the values didn’t change.
545                if **velocity != new_vel {
546                    **velocity = new_vel;
547                }
548            }
549
550            if let Some(sleeping) = &mut sleeping {
551                // NOTE: we write the new value only if there was an
552                //       actual change, in order to not trigger bevy’s
553                //       change tracking when the values didn’t change.
554                if sleeping.sleeping != rb.is_sleeping() {
555                    sleeping.sleeping = rb.is_sleeping();
556                }
557            }
558        }
559    }
560}
561
562/// System responsible for creating new Rapier rigid-bodies from the related `bevy_rapier` components.
563pub fn init_rigid_bodies(
564    mut commands: Commands,
565    default_context_access: Query<Entity, With<DefaultRapierContext>>,
566    mut rigidbody_sets: Query<(Entity, &mut RapierRigidBodySet)>,
567    rigid_bodies: Query<RigidBodyComponents, Without<RapierRigidBodyHandle>>,
568) {
569    for (
570        (entity, entity_context_link),
571        rb,
572        transform,
573        vel,
574        additional_mass_props,
575        _mass_props,
576        locked_axes,
577        force,
578        gravity_scale,
579        (ccd, soft_ccd),
580        dominance,
581        sleep,
582        damping,
583        disabled,
584        additional_solver_iters,
585    ) in rigid_bodies.iter()
586    {
587        let mut builder = RigidBodyBuilder::new((*rb).into());
588        builder = builder.enabled(disabled.is_none());
589
590        if let Some(transform) = transform {
591            builder = builder.pose(utils::transform_to_iso(&transform.compute_transform()));
592        }
593
594        #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
595        if let Some(vel) = vel {
596            builder = builder.linvel(vel.linvel.into()).angvel(vel.angvel.into());
597        }
598
599        if let Some(locked_axes) = locked_axes {
600            builder = builder.locked_axes((*locked_axes).into())
601        }
602
603        if let Some(gravity_scale) = gravity_scale {
604            builder = builder.gravity_scale(gravity_scale.0);
605        }
606
607        if let Some(ccd) = ccd {
608            builder = builder.ccd_enabled(ccd.enabled)
609        }
610
611        if let Some(soft_ccd) = soft_ccd {
612            builder = builder.soft_ccd_prediction(soft_ccd.prediction)
613        }
614
615        if let Some(dominance) = dominance {
616            builder = builder.dominance_group(dominance.groups)
617        }
618
619        if let Some(sleep) = sleep {
620            builder = builder.sleeping(sleep.sleeping);
621        }
622
623        if let Some(damping) = damping {
624            builder = builder
625                .linear_damping(damping.linear_damping)
626                .angular_damping(damping.angular_damping);
627        }
628
629        if let Some(mprops) = additional_mass_props {
630            builder = match mprops {
631                AdditionalMassProperties::MassProperties(mprops) => {
632                    builder.additional_mass_properties(mprops.into_rapier())
633                }
634                AdditionalMassProperties::Mass(mass) => builder.additional_mass(*mass),
635            };
636        }
637
638        if let Some(added_iters) = additional_solver_iters {
639            builder = builder.additional_solver_iterations(added_iters.0);
640        }
641
642        builder = builder.user_data(entity.to_bits() as u128);
643
644        let mut rb = builder.build();
645
646        #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
647        if let Some(force) = force {
648            rb.add_force(force.force.into(), false);
649            rb.add_torque(force.torque.into(), false);
650        }
651
652        // NOTE: we can’t apply impulses yet at this point because
653        //       the rigid-body’s mass isn’t up-to-date yet (its
654        //       attached colliders, if any, haven’t been created yet).
655
656        if let Some(sleep) = sleep {
657            let activation = rb.activation_mut();
658            activation.normalized_linear_threshold = sleep.normalized_linear_threshold;
659            activation.angular_threshold = sleep.angular_threshold;
660        }
661        // Get rapier context from RapierContextEntityLink or insert its default value.
662        let context_entity = entity_context_link.map_or_else(
663            || {
664                let context_entity = default_context_access.single().ok()?;
665                commands
666                    .entity(entity)
667                    .insert(RapierContextEntityLink(context_entity));
668                Some(context_entity)
669            },
670            |link| Some(link.0),
671        );
672        let Some(context_entity) = context_entity else {
673            continue;
674        };
675
676        let Ok((_, mut rigidbody_set)) = rigidbody_sets.get_mut(context_entity) else {
677            log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
678            continue;
679        };
680        let handle = rigidbody_set.bodies.insert(rb);
681        commands
682            .entity(entity)
683            .insert(RapierRigidBodyHandle(handle));
684        rigidbody_set.entity2body.insert(entity, handle);
685
686        if let Some(transform) = transform {
687            rigidbody_set
688                .last_body_transform_set
689                .insert(handle, *transform);
690        }
691    }
692}
693
694/// This applies the initial impulse given to a rigid-body when it is created.
695///
696/// This cannot be done inside `init_rigid_bodies` because impulses require the rigid-body
697/// mass to be available, which it was not because colliders were not created yet. As a
698/// result, we run this system after the collider creation.
699pub fn apply_initial_rigid_body_impulses(
700    mut context: Query<(&mut RapierRigidBodySet, &RapierContextColliders)>,
701    // We can’t use RapierRigidBodyHandle yet because its creation command hasn’t been
702    // executed yet.
703    mut init_impulses: Query<
704        (Entity, &RapierContextEntityLink, &mut ExternalImpulse),
705        Without<RapierRigidBodyHandle>,
706    >,
707) {
708    for (entity, link, mut impulse) in init_impulses.iter_mut() {
709        let (mut rigidbody_set, context_colliders) =
710            context.get_mut(link.0).expect(RAPIER_CONTEXT_EXPECT_ERROR);
711        let rigidbody_set = &mut *rigidbody_set;
712
713        let bodies = &mut rigidbody_set.bodies;
714        if let Some(rb) = rigidbody_set
715            .entity2body
716            .get(&entity)
717            .and_then(|h| bodies.get_mut(*h))
718        {
719            // Make sure the mass-properties are computed.
720            rb.recompute_mass_properties_from_colliders(&context_colliders.colliders);
721            // Apply the impulse.
722            rb.apply_impulse(impulse.impulse.into(), false);
723
724            #[allow(clippy::useless_conversion)] // Need to convert if dim3 enabled
725            rb.apply_torque_impulse(impulse.torque_impulse.into(), false);
726
727            impulse.reset();
728        }
729    }
730}