Skip to main content

bevy_rapier3d/plugin/systems/
joint.rs

1use crate::dynamics::free_joint_dofs;
2use crate::dynamics::ImpulseJoint;
3use crate::dynamics::ImpulseJointDisabled;
4use crate::dynamics::ImpulseJointImpulses;
5use crate::dynamics::KinematicMultibodyJoint;
6use crate::dynamics::MultibodyJoint;
7use crate::dynamics::MultibodyJointArmature;
8use crate::dynamics::MultibodyJointCoupling;
9use crate::dynamics::MultibodyJointCouplings;
10use crate::dynamics::MultibodyJointDamping;
11use crate::dynamics::MultibodyJointFriction;
12use crate::dynamics::MultibodyJointSprings;
13use crate::dynamics::MultibodyJointState;
14use crate::dynamics::MultibodySelfContactsDisabled;
15use crate::dynamics::RapierImpulseJointHandle;
16use crate::dynamics::RapierMultibodyJointHandle;
17use crate::plugin::context::systemparams::RAPIER_CONTEXT_EXPECT_ERROR;
18use crate::plugin::context::RapierContextEntityLink;
19use crate::plugin::context::RapierContextJoints;
20use crate::plugin::context::RapierRigidBodySet;
21use bevy::platform::collections::HashSet;
22use bevy::prelude::*;
23
24use super::RapierContextLinkResolver;
25use rapier::dynamics::{
26    GenericJoint as RapierGenericJoint, ImpulseJointHandle, ImpulseJointSet, JointEnabled,
27    Multibody, MultibodyDofCoupling, MultibodyJointHandle, MultibodyJointSet, RigidBodyHandle,
28    RigidBodySet,
29};
30use rapier::math::SPATIAL_DIM;
31
32/// Finds the rigid-body an [`ImpulseJoint`] of `entity` is attached to: the one of `entity`
33/// itself, or of its closest ancestor with a rigid-body.
34fn impulse_joint_target_body(
35    rigidbody_set: &RapierRigidBodySet,
36    child_of_query: &Query<&ChildOf>,
37    entity: Entity,
38) -> Option<RigidBodyHandle> {
39    let mut body_entity = entity;
40    loop {
41        if let Some(handle) = rigidbody_set.entity2body.get(&body_entity) {
42            return Some(*handle);
43        }
44        body_entity = child_of_query.get(body_entity).ok()?.parent();
45    }
46}
47
48/// Converts the joint description of an [`ImpulseJoint`] component into the Rapier joint data.
49fn impulse_joint_data(entity: Entity, joint: &ImpulseJoint, disabled: bool) -> RapierGenericJoint {
50    let mut data = joint.data.as_ref().into_rapier();
51    data.user_data = entity.to_bits() as u128;
52    if disabled {
53        data.set_enabled(false);
54    }
55    data
56}
57
58/// Converts the joint description of a [`MultibodyJoint`] component into the Rapier joint data.
59fn multibody_joint_data(entity: Entity, joint: &MultibodyJoint) -> RapierGenericJoint {
60    let mut data = joint.data.as_ref().into_rapier();
61    data.user_data = entity.to_bits() as u128;
62    data
63}
64
65/// Inserts a (possibly kinematic) multibody joint between two rigid-bodies.
66fn insert_multibody_joint(
67    joints: &mut MultibodyJointSet,
68    body1: RigidBodyHandle,
69    body2: RigidBodyHandle,
70    data: RapierGenericJoint,
71    kinematic: bool,
72) -> Option<MultibodyJointHandle> {
73    if kinematic {
74        joints.insert_kinematic(body1, body2, data, true)
75    } else {
76        joints.insert(body1, body2, data, true)
77    }
78}
79
80/// Writes the per-joint properties of a multibody joint, using the default values for the
81/// missing components.
82fn write_multibody_joint_properties(
83    mb: &mut Multibody,
84    link_id: usize,
85    kinematic: bool,
86    damping: Option<&MultibodyJointDamping>,
87    friction: Option<&MultibodyJointFriction>,
88    armature: Option<&MultibodyJointArmature>,
89    springs: Option<&MultibodyJointSprings>,
90) {
91    let Some(link) = mb.link(link_id) else {
92        return;
93    };
94    let offset = link.assembly_id();
95    let locked_axes = link.joint().data.locked_axes;
96    let damping = damping.copied().unwrap_or_default();
97    let friction = friction.copied().unwrap_or_default();
98    let armature = armature.copied().unwrap_or_default();
99    let springs = springs.copied().unwrap_or_default();
100
101    for (dof, axis) in free_joint_dofs(locked_axes) {
102        mb.damping_mut()[offset + dof] = damping.0[axis];
103        mb.frictions_mut()[offset + dof] = friction.0[axis];
104        mb.armature_mut()[offset + dof] = armature.0[axis];
105    }
106
107    let Some(link) = mb.link_mut(link_id) else {
108        return;
109    };
110    link.joint.kinematic = kinematic;
111    for axis in 0..SPATIAL_DIM {
112        if locked_axes.bits() & (1 << axis) == 0 {
113            link.joint
114                .set_spring(axis, springs.stiffness[axis], springs.rest[axis]);
115        } else {
116            link.joint.set_spring(axis, 0.0, 0.0);
117        }
118    }
119}
120
121/// Converts a [`MultibodyJointCoupling`] declared on `entity` into a Rapier coupling.
122///
123/// Returns `None` if the two joints are not part of the same multibody, or if one of the coupled
124/// axes is locked.
125fn rapier_dof_coupling(
126    joints: &MultibodyJointSet,
127    rigidbody_set: &RapierRigidBodySet,
128    entity: Entity,
129    coupling: &MultibodyJointCoupling,
130) -> Option<MultibodyDofCoupling> {
131    let link_id = |entity: &Entity| {
132        let body = rigidbody_set.entity2body.get(entity)?;
133        joints.rigid_body_link(*body)
134    };
135    let link1 = link_id(&coupling.source)?;
136    let link2 = link_id(&entity)?;
137    if link1.multibody != link2.multibody {
138        return None;
139    }
140
141    let mb = joints.get_multibody(link1.multibody)?;
142    let dof = |link: usize, axis: usize| {
143        free_joint_dofs(mb.link(link)?.joint().data.locked_axes)
144            .find(|(_, free_axis)| *free_axis == axis)
145            .map(|(dof, _)| dof)
146    };
147    let (axis1, axis2) = (coupling.source_axis as usize, coupling.axis as usize);
148    Some(MultibodyDofCoupling {
149        link1: link1.id,
150        dof1: dof(link1.id, axis1)?,
151        axis1,
152        link2: link2.id,
153        dof2: dof(link2.id, axis2)?,
154        axis2,
155        coeff: coupling.coeff,
156        offset: coupling.offset,
157    })
158}
159
160/// Makes the Rapier DoF couplings of the multibody joint of `entity` match its declared
161/// `couplings`: the stale ones are removed, and the missing ones that can be resolved are added.
162fn sync_dof_couplings(
163    joints: &mut MultibodyJointSet,
164    rigidbody_set: &RapierRigidBodySet,
165    entity: Entity,
166    handle: MultibodyJointHandle,
167    couplings: Option<&MultibodyJointCouplings>,
168) {
169    let declared: Vec<_> = couplings
170        .iter()
171        .flat_map(|couplings| couplings.0.iter())
172        .filter_map(|c| rapier_dof_coupling(joints, rigidbody_set, entity, c))
173        .collect();
174    let Some((mb, link_id)) = joints.get_mut(handle) else {
175        return;
176    };
177    // The couplings driving this joint are the ones declared on its entity.
178    mb.retain_dof_couplings(|c| {
179        c.link2 != link_id || declared.iter().any(|d| same_dof_coupling(c, d))
180    });
181    for coupling in declared {
182        if !mb
183            .couplings()
184            .iter()
185            .any(|c| same_dof_coupling(c, &coupling))
186        {
187            mb.add_dof_coupling(coupling);
188        }
189    }
190}
191
192/// Wakes up all the rigid-bodies of a multibody.
193pub(crate) fn wake_up_multibody(mb: &Multibody, bodies: &mut RigidBodySet) {
194    for link in mb.links() {
195        if let Some(body) = bodies.get_mut(link.rigid_body_handle()) {
196            body.wake_up(true);
197        }
198    }
199}
200
201/// Checks if two Rapier DoF couplings are identical.
202fn same_dof_coupling(a: &MultibodyDofCoupling, b: &MultibodyDofCoupling) -> bool {
203    (a.link1, a.dof1, a.axis1, a.link2, a.dof2, a.axis2)
204        == (b.link1, b.dof1, b.axis1, b.link2, b.dof2, b.axis2)
205        && a.coeff == b.coeff
206        && a.offset == b.offset
207}
208
209/// Enables or disables an impulse joint.
210///
211/// A joint re-enabled while attached to a disabled rigid-body stays disabled until that body is
212/// enabled again. Rapier updates the islands of joints accessed through `get_mut` at the next step.
213pub(crate) fn set_impulse_joint_enabled(
214    joints: &mut ImpulseJointSet,
215    bodies: &RigidBodySet,
216    handle: ImpulseJointHandle,
217    enabled: bool,
218) {
219    let Some(joint) = joints.get_mut(handle, true) else {
220        return;
221    };
222    let body_enabled = |h| bodies.get(h).is_none_or(|rb| rb.is_enabled());
223    joint.data.enabled = if !enabled {
224        JointEnabled::Disabled
225    } else if joint.data.enabled != JointEnabled::Disabled {
226        joint.data.enabled
227    } else if body_enabled(joint.body1()) && body_enabled(joint.body2()) {
228        JointEnabled::Enabled
229    } else {
230        JointEnabled::DisabledByAttachedBody
231    };
232}
233
234/// System responsible for creating new Rapier joints from the related `bevy_rapier` components.
235pub fn init_joints(
236    mut commands: Commands,
237    mut context_access: Query<(&RapierRigidBodySet, &mut RapierContextJoints)>,
238    context_links: RapierContextLinkResolver,
239    impulse_joints: Query<
240        (
241            Entity,
242            Option<&RapierContextEntityLink>,
243            &ImpulseJoint,
244            Has<ImpulseJointDisabled>,
245        ),
246        Without<RapierImpulseJointHandle>,
247    >,
248    multibody_joints: Query<
249        (
250            Entity,
251            Option<&RapierContextEntityLink>,
252            &MultibodyJoint,
253            Has<KinematicMultibodyJoint>,
254        ),
255        Without<RapierMultibodyJointHandle>,
256    >,
257    child_of_query: Query<&ChildOf>,
258) {
259    for (entity, entity_context_link, joint, disabled) in impulse_joints.iter() {
260        // Use the RapierContextEntityLink, or insert the context of an ancestor or the default one.
261        let context_entity = context_links.resolve(entity, entity_context_link, &mut commands);
262        let Some(context_entity) = context_entity else {
263            continue;
264        };
265
266        let Ok(rigidbody_set_joints) = context_access.get_mut(context_entity) else {
267            log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
268            continue;
269        };
270        let rigidbody_set = rigidbody_set_joints.0;
271        let target = impulse_joint_target_body(rigidbody_set, &child_of_query, entity);
272        let joints = rigidbody_set_joints.1.into_inner();
273
274        if let (Some(target), Some(source)) = (target, rigidbody_set.entity2body.get(&joint.parent))
275        {
276            let handle = joints.impulse_joints.insert(
277                *source,
278                target,
279                impulse_joint_data(entity, joint, disabled),
280                true,
281            );
282            commands
283                .entity(entity)
284                .insert(RapierImpulseJointHandle(handle));
285            joints.entity2impulse_joint.insert(entity, handle);
286        }
287    }
288
289    for (entity, entity_context_link, joint, kinematic) in multibody_joints.iter() {
290        // Use the RapierContextEntityLink, or insert the context of an ancestor or the default one.
291        let context_entity = context_links.resolve(entity, entity_context_link, &mut commands);
292        let Some(context_entity) = context_entity else {
293            continue;
294        };
295
296        let Ok(context_joints) = context_access.get_mut(context_entity) else {
297            log::error!("Could not find entity {context_entity} with rapier context while initializing {entity}");
298            continue;
299        };
300        let context = context_joints.0;
301        let target = context.entity2body.get(&entity);
302        let joints = context_joints.1.into_inner();
303
304        if let (Some(target), Some(source)) = (target, context.entity2body.get(&joint.parent)) {
305            if let Some(handle) = insert_multibody_joint(
306                &mut joints.multibody_joints,
307                *source,
308                *target,
309                multibody_joint_data(entity, joint),
310                kinematic,
311            ) {
312                commands
313                    .entity(entity)
314                    .insert(RapierMultibodyJointHandle(handle));
315                joints.entity2multibody_joint.insert(entity, handle);
316            } else {
317                log::error!("Failed to create multibody joint: loop detected.")
318            }
319        }
320    }
321}
322
323/// System responsible for applying changes the user made to a joint component.
324///
325/// This also re-attaches an [`ImpulseJoint`] to other rigid-bodies if its `parent`, or the
326/// rigid-body of its entity, changed, and a [`MultibodyJoint`] if its `parent` changed. If one of
327/// the new bodies doesn’t exist yet, the joint is removed from the physics scene until
328/// [`init_joints`] can create it again.
329#[allow(clippy::type_complexity)]
330pub fn apply_joint_user_changes(
331    mut commands: Commands,
332    mut context: Query<(&RapierRigidBodySet, &mut RapierContextJoints)>,
333    changed_impulse_joints: Query<
334        (
335            Entity,
336            &RapierContextEntityLink,
337            &RapierImpulseJointHandle,
338            &ImpulseJoint,
339            Has<ImpulseJointDisabled>,
340        ),
341        Or<(
342            Changed<ImpulseJoint>,
343            Changed<ImpulseJointDisabled>,
344            Changed<ChildOf>,
345        )>,
346    >,
347    changed_multibody_joints: Query<
348        (
349            Entity,
350            &RapierContextEntityLink,
351            &RapierMultibodyJointHandle,
352            &MultibodyJoint,
353            Has<KinematicMultibodyJoint>,
354        ),
355        Changed<MultibodyJoint>,
356    >,
357    child_of_query: Query<&ChildOf>,
358) {
359    for (entity, link, handle, changed_joint, disabled) in changed_impulse_joints.iter() {
360        let (rigidbody_set, mut context) =
361            context.get_mut(link.0).expect(RAPIER_CONTEXT_EXPECT_ERROR);
362        let joints = &mut context.impulse_joints;
363
364        let body1 = rigidbody_set
365            .entity2body
366            .get(&changed_joint.parent)
367            .copied();
368        let body2 = impulse_joint_target_body(rigidbody_set, &child_of_query, entity);
369        let (Some(body1), Some(body2), true) = (body1, body2, joints.contains(handle.0)) else {
370            // Detach the joint until its bodies exist; `init_joints` will then re-create it.
371            joints.remove(handle.0, true);
372            context.entity2impulse_joint.remove(&entity);
373            commands.entity(entity).remove::<RapierImpulseJointHandle>();
374            continue;
375        };
376
377        let Some(joint) = joints.set_bodies(handle.0, body1, body2, true) else {
378            continue;
379        };
380
381        // Keep the solver’s warmstart impulses and let `set_impulse_joint_enabled` handle the
382        // enabled state, since Rapier may have disabled the joint because of its bodies.
383        let old_data = joint.data;
384        let mut new_data = impulse_joint_data(entity, changed_joint, false);
385        new_data.enabled = old_data.enabled;
386        for (new_limit, old_limit) in new_data.limits.iter_mut().zip(old_data.limits.iter()) {
387            new_limit.impulse = old_limit.impulse;
388        }
389        for (new_motor, old_motor) in new_data.motors.iter_mut().zip(old_data.motors.iter()) {
390            new_motor.impulse = old_motor.impulse;
391        }
392        joint.data = new_data;
393
394        let enabled = changed_joint.data.as_ref().is_enabled() && !disabled;
395        set_impulse_joint_enabled(joints, &rigidbody_set.bodies, handle.0, enabled);
396    }
397
398    for (entity, link, handle, changed_joint, kinematic) in changed_multibody_joints.iter() {
399        let (rigidbody_set, mut context) =
400            context.get_mut(link.0).expect(RAPIER_CONTEXT_EXPECT_ERROR);
401        let context = &mut *context;
402        let data = multibody_joint_data(entity, changed_joint);
403        let new_parent = rigidbody_set
404            .entity2body
405            .get(&changed_joint.parent)
406            .copied();
407
408        let Some((mb, link_id)) = context.multibody_joints.get_mut(handle.0) else {
409            continue;
410        };
411        let Some(mb_link) = mb.link(link_id) else {
412            continue;
413        };
414        let body = mb_link.rigid_body_handle();
415        let old_parent = mb_link
416            .parent_id()
417            .and_then(|id| mb.link(id))
418            .map(|parent| parent.rigid_body_handle());
419
420        if old_parent.is_some() && old_parent == new_parent {
421            // TODO: not sure this will always work properly, e.g., if the number of Dofs is changed.
422            if let Some(mb_link) = mb.link_mut(link_id) {
423                mb_link.joint.data = data;
424            }
425            continue;
426        }
427
428        // The parent changed: re-attach the joint to the new parent.
429        context.multibody_joints.remove(handle.0, true);
430        let new_handle = new_parent.and_then(|parent| {
431            insert_multibody_joint(&mut context.multibody_joints, parent, body, data, kinematic)
432        });
433        if let Some(new_handle) = new_handle {
434            context.entity2multibody_joint.insert(entity, new_handle);
435            // Insert the handle even if unchanged: this flags the multibody topology change.
436            commands
437                .entity(entity)
438                .insert(RapierMultibodyJointHandle(new_handle));
439        } else {
440            if new_parent.is_some() {
441                log::error!("Failed to re-attach multibody joint: loop detected.");
442            }
443            // Detach the joint; `init_joints` will then try to re-create it.
444            context.entity2multibody_joint.remove(&entity);
445            commands
446                .entity(entity)
447                .remove::<RapierMultibodyJointHandle>();
448        }
449    }
450}
451
452/// Components configuring a multibody joint, read by [`apply_multibody_joint_properties`].
453type MultibodyJointPropertiesComponents = (
454    &'static RapierContextEntityLink,
455    &'static RapierMultibodyJointHandle,
456    Has<KinematicMultibodyJoint>,
457    Option<&'static MultibodyJointDamping>,
458    Option<&'static MultibodyJointFriction>,
459    Option<&'static MultibodyJointArmature>,
460    Option<&'static MultibodyJointSprings>,
461    Option<&'static MultibodyJointCouplings>,
462);
463
464/// Filter selecting the multibody joints with modified properties.
465type ChangedMultibodyJointProperties = Or<(
466    Added<RapierMultibodyJointHandle>,
467    Changed<MultibodyJoint>,
468    Changed<KinematicMultibodyJoint>,
469    Changed<MultibodyJointDamping>,
470    Changed<MultibodyJointFriction>,
471    Changed<MultibodyJointArmature>,
472    Changed<MultibodyJointSprings>,
473    Changed<MultibodyJointCouplings>,
474)>;
475
476/// System responsible for applying the properties of multibody joints and multibodies set by the
477/// user through the [`KinematicMultibodyJoint`], [`MultibodyJointDamping`],
478/// [`MultibodyJointFriction`], [`MultibodyJointArmature`], [`MultibodyJointSprings`],
479/// [`MultibodyJointCouplings`] and [`MultibodySelfContactsDisabled`] components.
480///
481/// The properties are applied when the joint is created, and when these components are modified
482/// or removed. DoF couplings and disabled self-contacts are also applied again when a multibody
483/// joint is created or re-attached, since they might only be resolvable once joints are merged
484/// into the same multibody.
485#[allow(clippy::type_complexity)]
486pub fn apply_multibody_joint_properties(
487    mut context: Query<(&mut RapierRigidBodySet, &mut RapierContextJoints)>,
488    changed_joints: Query<Entity, (With<MultibodyJoint>, ChangedMultibodyJointProperties)>,
489    changed_couplings: Query<Entity, Changed<MultibodyJointCouplings>>,
490    changed_topology: Query<
491        &RapierContextEntityLink,
492        (With<MultibodyJoint>, Changed<RapierMultibodyJointHandle>),
493    >,
494    joints: Query<MultibodyJointPropertiesComponents, With<MultibodyJoint>>,
495    all_couplings: Query<(
496        Entity,
497        &RapierContextEntityLink,
498        &RapierMultibodyJointHandle,
499        &MultibodyJointCouplings,
500    )>,
501    self_contacts_disabled: Query<(
502        Entity,
503        &RapierContextEntityLink,
504        Ref<MultibodySelfContactsDisabled>,
505    )>,
506    (
507        mut removed_kinematic,
508        mut removed_damping,
509        mut removed_friction,
510        mut removed_armature,
511        mut removed_springs,
512        mut removed_couplings,
513    ): (
514        RemovedComponents<KinematicMultibodyJoint>,
515        RemovedComponents<MultibodyJointDamping>,
516        RemovedComponents<MultibodyJointFriction>,
517        RemovedComponents<MultibodyJointArmature>,
518        RemovedComponents<MultibodyJointSprings>,
519        RemovedComponents<MultibodyJointCouplings>,
520    ),
521    mut removed_self_contacts_disabled: RemovedComponents<MultibodySelfContactsDisabled>,
522) {
523    let mut couplings_modified: HashSet<Entity> = changed_couplings.iter().collect();
524    couplings_modified.extend(removed_couplings.read());
525    let mut modified: HashSet<Entity> = changed_joints.iter().collect();
526    modified.extend(couplings_modified.iter().copied());
527    modified.extend(removed_kinematic.read());
528    modified.extend(removed_damping.read());
529    modified.extend(removed_friction.read());
530    modified.extend(removed_armature.read());
531    modified.extend(removed_springs.read());
532    // The contexts where multibody joints were created or re-attached.
533    let topology_changed: HashSet<Entity> = changed_topology.iter().map(|link| link.0).collect();
534
535    for entity in modified {
536        let Ok((link, handle, kinematic, damping, friction, armature, springs, couplings)) =
537            joints.get(entity)
538        else {
539            continue;
540        };
541        let Ok((mut rigidbody_set, mut context)) = context.get_mut(link.0) else {
542            continue;
543        };
544        let (rigidbody_set, context) = (&mut *rigidbody_set, &mut *context);
545
546        if couplings_modified.contains(&entity) {
547            sync_dof_couplings(
548                &mut context.multibody_joints,
549                rigidbody_set,
550                entity,
551                handle.0,
552                couplings,
553            );
554        }
555
556        if let Some((mb, link_id)) = context.multibody_joints.get_mut(handle.0) {
557            write_multibody_joint_properties(
558                mb, link_id, kinematic, damping, friction, armature, springs,
559            );
560            wake_up_multibody(mb, &mut rigidbody_set.bodies);
561        }
562    }
563
564    // Rapier keeps the couplings across topology changes, but the ones that couldn't be resolved
565    // yet (e.g. joints in different multibodies) might be now.
566    for (entity, link, handle, couplings) in all_couplings.iter() {
567        if !topology_changed.contains(&link.0) {
568            continue;
569        }
570        let Ok((rigidbody_set, mut context)) = context.get_mut(link.0) else {
571            continue;
572        };
573        sync_dof_couplings(
574            &mut context.multibody_joints,
575            &rigidbody_set,
576            entity,
577            handle.0,
578            Some(couplings),
579        );
580    }
581
582    // Enable self-contacts again when the marker is removed. If another link still has the
583    // marker, the multibody is disabled again right after.
584    let mut any_marker_removed = false;
585    for entity in removed_self_contacts_disabled.read() {
586        if !self_contacts_disabled.contains(entity) {
587            set_multibody_self_contacts(&mut context, entity, true);
588            any_marker_removed = true;
589        }
590    }
591    // Rapier keeps self-contacts disabled when multibodies merge, but a marker added before its
592    // rigid-body joined a multibody has to be applied once it does.
593    for (entity, link, marker) in self_contacts_disabled.iter() {
594        if any_marker_removed || marker.is_changed() || topology_changed.contains(&link.0) {
595            set_multibody_self_contacts(&mut context, entity, false);
596        }
597    }
598}
599
600/// System responsible for writing the impulses applied by impulse joints back into the
601/// [`ImpulseJointImpulses`] component.
602pub fn writeback_impulse_joint_impulses(
603    context: Query<&RapierContextJoints>,
604    mut impulse_joints: Query<(
605        &RapierContextEntityLink,
606        &RapierImpulseJointHandle,
607        &mut ImpulseJointImpulses,
608    )>,
609) {
610    for (link, handle, mut impulses) in impulse_joints.iter_mut() {
611        let Some(joint) = context
612            .get(link.0)
613            .ok()
614            .and_then(|joints| joints.impulse_joints.get(handle.0))
615        else {
616            continue;
617        };
618
619        // A disabled joint isn't solved, so its last impulses are stale.
620        let new_impulses = if joint.data.is_enabled() {
621            ImpulseJointImpulses::from_rapier(joint)
622        } else {
623            ImpulseJointImpulses::default()
624        };
625        // NOTE: only write actual changes to avoid triggering bevy's change detection.
626        impulses.set_if_neq(new_impulses);
627    }
628}
629
630/// Enables or disables the self-contacts of the multibody containing the rigid-body of `entity`.
631fn set_multibody_self_contacts(
632    contexts: &mut Query<(&mut RapierRigidBodySet, &mut RapierContextJoints)>,
633    entity: Entity,
634    enabled: bool,
635) {
636    for (rigidbody_set, mut context) in contexts.iter_mut() {
637        let Some(body) = rigidbody_set.entity2body.get(&entity) else {
638            continue;
639        };
640        let Some(link) = context.multibody_joints.rigid_body_link(*body).copied() else {
641            return;
642        };
643        let needs_change = context
644            .multibody_joints
645            .get_multibody(link.multibody)
646            .is_some_and(|mb| mb.self_contacts_enabled() != enabled);
647        if needs_change {
648            if let Some(mb) = context.multibody_joints.get_multibody_mut(link.multibody) {
649                mb.set_self_contacts_enabled(enabled);
650            }
651        }
652        return;
653    }
654}
655
656/// System responsible for writing the state of multibody joints back into the
657/// [`MultibodyJointState`] component.
658pub fn writeback_multibody_joint_states(
659    context: Query<&RapierContextJoints>,
660    mut multibody_joints: Query<(
661        &RapierContextEntityLink,
662        &RapierMultibodyJointHandle,
663        &mut MultibodyJointState,
664    )>,
665) {
666    for (link, handle, mut state) in multibody_joints.iter_mut() {
667        let Some(new_state) = context
668            .get(link.0)
669            .ok()
670            .and_then(|joints| joints.multibody_joint_state_from_handle(handle.0))
671        else {
672            continue;
673        };
674        // NOTE: only write actual changes to avoid triggering bevy's change detection.
675        state.set_if_neq(new_state);
676    }
677}
678
679#[cfg(test)]
680mod tests {
681    use super::*;
682    use crate::prelude::*;
683    use bevy::time::{TimePlugin, TimeUpdateStrategy};
684
685    fn test_app() -> App {
686        let mut app = App::new();
687        app.add_plugins((
688            TransformPlugin,
689            TimePlugin,
690            RapierPhysicsPlugin::<NoUserData>::default(),
691        ))
692        .insert_resource(TimeUpdateStrategy::ManualDuration(
693            std::time::Duration::from_secs_f32(1.0 / 60.0),
694        ));
695        app.finish();
696        app.update();
697        app
698    }
699
700    fn run(app: &mut App, frames: usize) {
701        for _ in 0..frames {
702            app.update();
703        }
704    }
705
706    fn spawn_body(app: &mut App, body: RigidBody, x: f32, y: f32) -> Entity {
707        app.world_mut()
708            .spawn((Transform::from_xyz(x, y, 0.0), body, Collider::ball(0.1)))
709            .id()
710    }
711
712    fn translation(app: &App, entity: Entity) -> Vec3 {
713        app.world().get::<Transform>(entity).unwrap().translation
714    }
715
716    fn with_joints<R>(
717        app: &mut App,
718        f: impl FnOnce(&RapierRigidBodySet, &RapierContextJoints) -> R,
719    ) -> R {
720        let world = app.world_mut();
721        let (bodies, joints) = world
722            .query::<(&RapierRigidBodySet, &RapierContextJoints)>()
723            .single(world)
724            .unwrap();
725        f(bodies, joints)
726    }
727
728    fn raw_joint<R>(
729        app: &mut App,
730        entity: Entity,
731        f: impl FnOnce(&RapierRigidBodySet, &rapier::dynamics::ImpulseJoint) -> R,
732    ) -> R {
733        with_joints(app, |bodies, joints| {
734            let handle = joints.entity2impulse_joint()[&entity];
735            f(bodies, joints.impulse_joints.get(handle).unwrap())
736        })
737    }
738
739    #[cfg(feature = "dim2")]
740    #[test]
741    fn pin_slot_joint_constrains_motion() {
742        let mut app = test_app();
743        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
744        let body = spawn_body(&mut app, RigidBody::Dynamic, 0.0, 0.0);
745        app.world_mut().entity_mut(body).insert((
746            Velocity {
747                linear: Vect::new(1.0, 0.0),
748                angular: 2.0,
749            },
750            ImpulseJoint::new(
751                ground,
752                PinSlotJointBuilder::new(Vect::X).contacts_enabled(false),
753            ),
754        ));
755        run(&mut app, 60);
756
757        let pos = translation(&app, body);
758        assert!(pos.x > 0.5, "the body should slide along the slot: {pos}");
759        assert!(pos.y.abs() < 1.0e-2, "the body should not fall: {pos}");
760        let angvel = app.world().get::<Velocity>(body).unwrap().angular;
761        assert!(angvel.abs() > 1.0, "rotations should be free: {angvel}");
762
763        let joint = app.world().get::<ImpulseJoint>(body).unwrap();
764        assert!(matches!(joint.data, TypedJoint::PinSlotJoint(_)));
765        assert!(joint.data.as_ref().as_pin_slot().is_some());
766        assert!(joint.data.as_ref().as_prismatic().is_none());
767    }
768
769    #[test]
770    fn disabled_impulse_joint_lets_bodies_separate() {
771        let mut app = test_app();
772        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
773        let body = spawn_body(&mut app, RigidBody::Dynamic, 0.0, 0.0);
774        let joint = FixedJointBuilder::new().contacts_enabled(false);
775        app.world_mut()
776            .entity_mut(body)
777            .insert((ImpulseJoint::new(ground, joint), ImpulseJointDisabled));
778        run(&mut app, 30);
779
780        assert!(translation(&app, body).y < -0.5, "the body should fall");
781        raw_joint(&mut app, body, |_, j| assert!(!j.data.is_enabled()));
782        with_joints(&mut app, |bodies, joints| {
783            assert_eq!(joints.attached_impulse_joints(bodies, ground).count(), 1);
784            assert_eq!(
785                joints
786                    .attached_enabled_impulse_joints(bodies, ground)
787                    .count(),
788                0
789            );
790        });
791        assert_eq!(
792            *app.world().get::<ImpulseJointImpulses>(body).unwrap(),
793            ImpulseJointImpulses::default()
794        );
795
796        app.world_mut()
797            .entity_mut(body)
798            .remove::<ImpulseJointDisabled>();
799        run(&mut app, 120);
800
801        raw_joint(&mut app, body, |_, j| assert!(j.data.is_enabled()));
802        let pos = translation(&app, body);
803        assert!(
804            pos.length() < 0.1,
805            "the joint should pull the body back: {pos}"
806        );
807
808        app.world_mut()
809            .entity_mut(body)
810            .insert(ImpulseJointDisabled);
811        run(&mut app, 30);
812        raw_joint(&mut app, body, |_, j| assert!(!j.data.is_enabled()));
813        assert!(
814            translation(&app, body).y < -0.5,
815            "the body should fall again"
816        );
817    }
818
819    #[test]
820    fn disabled_impulse_joint_splits_islands() {
821        let mut app = test_app();
822        let body1 = spawn_body(&mut app, RigidBody::Dynamic, 0.0, 0.0);
823        let body2 = spawn_body(&mut app, RigidBody::Dynamic, 2.0, 0.0);
824        app.world_mut()
825            .entity_mut(body1)
826            .insert((GravityScale(0.0), Sleeping::default()));
827        app.world_mut().entity_mut(body2).insert((
828            GravityScale(0.0),
829            Sleeping::default(),
830            ImpulseJoint::new(body1, FixedJointBuilder::new().local_anchor1(Vect::X * 2.0)),
831        ));
832        run(&mut app, 2);
833
834        // With the joint disabled, the resting body falls asleep while the other keeps moving.
835        app.world_mut()
836            .entity_mut(body2)
837            .insert((ImpulseJointDisabled, Velocity::linear(Vect::X)));
838        run(&mut app, 240);
839        let sleeping = |app: &App, entity| app.world().get::<Sleeping>(entity).unwrap().sleeping;
840        assert!(sleeping(&app, body1));
841        assert!(!sleeping(&app, body2));
842
843        // Re-enabling the joint merges the islands again, waking the resting body up.
844        app.world_mut()
845            .entity_mut(body2)
846            .remove::<ImpulseJointDisabled>();
847        run(&mut app, 2);
848        assert!(!sleeping(&app, body1));
849    }
850
851    #[test]
852    fn impulses_are_written_back() {
853        let mut app = test_app();
854        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
855        let body = spawn_body(&mut app, RigidBody::Dynamic, 1.0, 0.0);
856        app.world_mut().entity_mut(body).insert((
857            AdditionalMassProperties::Mass(10.0),
858            ImpulseJoint::new(
859                ground,
860                FixedJointBuilder::new()
861                    .local_anchor1(Vect::X)
862                    .contacts_enabled(false),
863            ),
864        ));
865        run(&mut app, 10);
866
867        let impulses = *app.world().get::<ImpulseJointImpulses>(body).unwrap();
868        assert!(
869            impulses.linear.length() > 1.0e-2,
870            "the joint should hold the body against gravity: {impulses:?}"
871        );
872        let from_context = with_joints(&mut app, |_, joints| {
873            joints.impulse_joint_impulses(body).unwrap()
874        });
875        assert_eq!(impulses, from_context);
876    }
877
878    #[test]
879    fn reparenting_moves_the_joint() {
880        let mut app = test_app();
881        let ground1 = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
882        let ground2 = spawn_body(&mut app, RigidBody::Fixed, 5.0, 0.0);
883        let body = spawn_body(&mut app, RigidBody::Dynamic, 0.0, -1.0);
884        app.world_mut().entity_mut(body).insert(ImpulseJoint::new(
885            ground1,
886            FixedJointBuilder::new().local_anchor1(-Vect::Y),
887        ));
888        run(&mut app, 2);
889
890        let handle = app.world().get::<RapierImpulseJointHandle>(body).unwrap().0;
891        with_joints(&mut app, |bodies, joints| {
892            let between: Vec<_> = joints
893                .impulse_joints_between(bodies, ground1, body)
894                .collect();
895            assert_eq!(between, vec![body]);
896            let between: Vec<_> = joints
897                .impulse_joints_between(bodies, body, ground1)
898                .collect();
899            assert_eq!(between, vec![body]);
900            assert_eq!(
901                joints.impulse_joints_between(bodies, ground2, body).count(),
902                0
903            );
904            assert_eq!(joints.impulse_joint_entity(handle), Some(body));
905        });
906
907        app.world_mut()
908            .get_mut::<ImpulseJoint>(body)
909            .unwrap()
910            .parent = ground2;
911        run(&mut app, 120);
912
913        assert_eq!(
914            app.world().get::<RapierImpulseJointHandle>(body).unwrap().0,
915            handle
916        );
917        with_joints(&mut app, |bodies, joints| {
918            assert_eq!(
919                joints.impulse_joints_between(bodies, ground1, body).count(),
920                0
921            );
922            let between: Vec<_> = joints
923                .impulse_joints_between(bodies, ground2, body)
924                .collect();
925            assert_eq!(between, vec![body]);
926            let raw = joints.impulse_joints.get(handle).unwrap();
927            assert_eq!(raw.body1(), bodies.entity2body()[&ground2]);
928        });
929        let pos = translation(&app, body);
930        assert!((pos.x - 5.0).abs() < 0.1, "the body should follow: {pos}");
931
932        // Re-parenting to an entity without rigid-body detaches the joint until it gets one.
933        let pending = app
934            .world_mut()
935            .spawn(Transform::from_xyz(-5.0, 0.0, 0.0))
936            .id();
937        app.world_mut()
938            .get_mut::<ImpulseJoint>(body)
939            .unwrap()
940            .parent = pending;
941        run(&mut app, 2);
942        assert!(app.world().get::<RapierImpulseJointHandle>(body).is_none());
943        with_joints(&mut app, |_, joints| {
944            assert!(joints.impulse_joints.is_empty())
945        });
946
947        app.world_mut().entity_mut(pending).insert(RigidBody::Fixed);
948        run(&mut app, 2);
949        raw_joint(&mut app, body, |bodies, raw| {
950            assert_eq!(raw.body1(), bodies.entity2body()[&pending]);
951        });
952    }
953
954    #[test]
955    fn softness_round_trip() {
956        let softness = SpringCoefficients::new(10.0, 0.5);
957        let mut joint = PrismaticJointBuilder::new(Vect::X)
958            .softness(softness)
959            .build();
960        assert_eq!(joint.softness(), softness);
961        let other = SpringCoefficients::new(20.0, 1.0);
962        joint.set_softness(other);
963        assert_eq!(joint.softness(), other);
964        assert_eq!(joint.data.softness(), other);
965        assert_eq!(
966            GenericJointBuilder::new(JointAxesMask::LOCKED_FIXED_AXES)
967                .softness(softness)
968                .build()
969                .softness(),
970            softness
971        );
972
973        let mut app = test_app();
974        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
975        let body = spawn_body(&mut app, RigidBody::Dynamic, 0.0, 0.0);
976        app.world_mut()
977            .entity_mut(body)
978            .insert(ImpulseJoint::new(ground, joint));
979        run(&mut app, 1);
980        raw_joint(&mut app, body, |_, j| assert_eq!(j.data.softness, other));
981
982        app.world_mut()
983            .get_mut::<ImpulseJoint>(body)
984            .unwrap()
985            .data
986            .as_mut()
987            .set_softness(softness);
988        run(&mut app, 1);
989        raw_joint(&mut app, body, |_, j| assert_eq!(j.data.softness, softness));
990    }
991
992    #[cfg(feature = "dim3")]
993    #[test]
994    fn spherical_joint_frames() {
995        let basis1 = Quat::from_rotation_z(0.5);
996        let basis2 = Quat::from_rotation_x(-0.3);
997        let joint = SphericalJointBuilder::new()
998            .local_anchor1(Vect::X)
999            .local_basis1(basis1)
1000            .local_basis2(basis2)
1001            .contacts_enabled(false)
1002            .build();
1003        assert!(joint.local_basis1().abs_diff_eq(basis1, 1.0e-6));
1004        assert!(joint.local_basis2().abs_diff_eq(basis2, 1.0e-6));
1005        assert_eq!(joint.local_frame1().translation, Vect::X);
1006        assert!(!joint.contacts_enabled());
1007
1008        let mut other = SphericalJoint::new();
1009        other.set_local_frame1(joint.local_frame1());
1010        assert_eq!(other.local_frame1(), joint.local_frame1());
1011        assert_eq!(other.local_anchor1(), Vect::X);
1012    }
1013
1014    #[test]
1015    fn changing_a_joint_wakes_up_its_bodies() {
1016        let mut app = test_app();
1017        let body1 = spawn_body(&mut app, RigidBody::Dynamic, 0.0, 0.0);
1018        let body2 = spawn_body(&mut app, RigidBody::Dynamic, 2.0, 0.0);
1019        app.world_mut()
1020            .entity_mut(body1)
1021            .insert((GravityScale(0.0), Sleeping::default()));
1022        app.world_mut().entity_mut(body2).insert((
1023            GravityScale(0.0),
1024            Sleeping::default(),
1025            ImpulseJoint::new(body1, FixedJointBuilder::new().local_anchor1(Vect::X * 2.0)),
1026        ));
1027        run(&mut app, 120);
1028        assert!(app.world().get::<Sleeping>(body1).unwrap().sleeping);
1029        assert!(app.world().get::<Sleeping>(body2).unwrap().sleeping);
1030
1031        app.world_mut()
1032            .get_mut::<ImpulseJoint>(body2)
1033            .unwrap()
1034            .data
1035            .as_mut()
1036            .set_contacts_enabled(false);
1037        app.update();
1038
1039        with_joints(&mut app, |bodies, _| {
1040            for entity in [body1, body2] {
1041                let rb = &bodies.bodies[bodies.entity2body()[&entity]];
1042                assert!(!rb.is_sleeping());
1043            }
1044        });
1045    }
1046
1047    fn with_joints_mut<R>(
1048        app: &mut App,
1049        f: impl FnOnce(&mut RapierRigidBodySet, &mut RapierContextJoints) -> R,
1050    ) -> R {
1051        let world = app.world_mut();
1052        let (mut bodies, mut joints) = world
1053            .query::<(&mut RapierRigidBodySet, &mut RapierContextJoints)>()
1054            .single_mut(world)
1055            .unwrap();
1056        f(&mut bodies, &mut joints)
1057    }
1058
1059    fn vect(x: f32, y: f32) -> Vect {
1060        #[cfg(feature = "dim2")]
1061        return Vect::new(x, y);
1062        #[cfg(feature = "dim3")]
1063        return Vect::new(x, y, 0.0);
1064    }
1065
1066    /// The rotational axis of the joints returned by [`revolute`], in the joint frame.
1067    const ROT_AXIS: JointAxis = JointAxis::AngX;
1068
1069    /// A revolute joint rotating in the XY plane, with its second anchor one unit behind the
1070    /// second body along X, and its first anchor at `anchor1`.
1071    fn revolute(anchor1: Vect) -> TypedJoint {
1072        #[cfg(feature = "dim2")]
1073        let builder = RevoluteJointBuilder::new();
1074        #[cfg(feature = "dim3")]
1075        let builder = RevoluteJointBuilder::new(Vect::Z);
1076        builder
1077            .local_anchor1(anchor1)
1078            .local_anchor2(-Vect::X)
1079            .build()
1080            .into()
1081    }
1082
1083    /// A prismatic joint sliding along X, with its first anchor at `anchor1`.
1084    fn prismatic(anchor1: Vect) -> TypedJoint {
1085        PrismaticJointBuilder::new(Vect::X)
1086            .local_anchor1(anchor1)
1087            .build()
1088            .into()
1089    }
1090
1091    /// Spawns a dynamic body with unit mass attached to `parent` by a multibody joint.
1092    fn spawn_link(app: &mut App, parent: Entity, pos: Vect, joint: TypedJoint) -> Entity {
1093        let entity = spawn_body(app, RigidBody::Dynamic, pos.x, pos.y);
1094        app.world_mut().entity_mut(entity).insert((
1095            ColliderMassProperties::Mass(1.0),
1096            MultibodyJoint::new(parent, joint),
1097        ));
1098        entity
1099    }
1100
1101    fn joint_state(app: &App, entity: Entity) -> MultibodyJointState {
1102        *app.world().get::<MultibodyJointState>(entity).unwrap()
1103    }
1104
1105    #[test]
1106    fn kinematic_multibody_joint_ignores_gravity() {
1107        let mut app = test_app();
1108        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1109        let arm = spawn_link(&mut app, ground, Vect::X, revolute(Vect::ZERO));
1110        app.world_mut()
1111            .entity_mut(arm)
1112            .insert(KinematicMultibodyJoint);
1113        run(&mut app, 60);
1114
1115        let pos = translation(&app, arm);
1116        assert!(
1117            (pos.x - 1.0).abs() < 1.0e-3 && pos.y.abs() < 1.0e-3,
1118            "the kinematic arm should not fall: {pos}"
1119        );
1120        with_joints(&mut app, |bodies, joints| {
1121            let (mb, link_id) = joints.multibody(bodies, arm).unwrap();
1122            assert!(mb.link(link_id).unwrap().joint().kinematic);
1123        });
1124
1125        // A kinematic joint follows the velocities set by the user.
1126        with_joints_mut(&mut app, |bodies, joints| {
1127            joints.multibody_joint_velocity_mut(bodies, arm).unwrap()[0] = 1.0;
1128        });
1129        run(&mut app, 30);
1130        let angle = joint_state(&app, arm).coords[ROT_AXIS as usize];
1131        assert!((angle - 0.5).abs() < 0.05, "the arm should rotate: {angle}");
1132
1133        app.world_mut()
1134            .entity_mut(arm)
1135            .remove::<KinematicMultibodyJoint>();
1136        with_joints_mut(&mut app, |bodies, joints| {
1137            joints.multibody_joint_velocity_mut(bodies, arm).unwrap()[0] = 0.0;
1138        });
1139        run(&mut app, 30);
1140        with_joints(&mut app, |bodies, joints| {
1141            let (mb, link_id) = joints.multibody(bodies, arm).unwrap();
1142            assert!(!mb.link(link_id).unwrap().joint().kinematic);
1143        });
1144        let angle = joint_state(&app, arm).coords[ROT_AXIS as usize];
1145        assert!(angle < 0.3, "the arm should fall once dynamic: {angle}");
1146    }
1147
1148    #[test]
1149    fn multibody_joint_friction_and_damping_slow_down_the_joint() {
1150        let mut app = test_app();
1151        let mut links = vec![];
1152        for (i, y) in [0.0, 5.0, 10.0].into_iter().enumerate() {
1153            let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, y);
1154            let link = spawn_link(&mut app, ground, vect(0.0, y), prismatic(Vect::ZERO));
1155            match i {
1156                1 => {
1157                    let friction = MultibodyJointFriction::default().with(JointAxis::LinX, 1.0);
1158                    app.world_mut().entity_mut(link).insert(friction);
1159                }
1160                2 => {
1161                    let damping = MultibodyJointDamping::default().with(JointAxis::LinX, 1.0);
1162                    app.world_mut().entity_mut(link).insert(damping);
1163                }
1164                _ => {}
1165            }
1166            links.push(link);
1167        }
1168        run(&mut app, 1);
1169        with_joints_mut(&mut app, |bodies, joints| {
1170            for link in &links {
1171                joints.multibody_joint_velocity_mut(bodies, *link).unwrap()[0] = 2.0;
1172            }
1173        });
1174        run(&mut app, 30);
1175
1176        let [free, friction, damping] =
1177            std::array::from_fn(|i| joint_state(&app, links[i]).velocities[0]);
1178        assert!(free > 1.95, "the free joint should keep moving: {free}");
1179        assert!(
1180            friction < 1.8 && friction > 0.0,
1181            "friction should slow down the joint: {friction}"
1182        );
1183        assert!(
1184            damping < 1.8 && damping > 0.0,
1185            "damping should slow down the joint: {damping}"
1186        );
1187
1188        // Removing the friction lets the joint move freely again.
1189        app.world_mut()
1190            .entity_mut(links[1])
1191            .remove::<MultibodyJointFriction>();
1192        run(&mut app, 1);
1193        with_joints(&mut app, |bodies, joints| {
1194            let (mb, _) = joints.multibody(bodies, links[1]).unwrap();
1195            let dofs = joints.multibody_link_dofs(bodies, links[1]).unwrap();
1196            assert_eq!(dofs.len(), 1);
1197            assert_eq!(mb.frictions()[dofs.start], 0.0);
1198        });
1199    }
1200
1201    #[test]
1202    fn multibody_joint_spring_pulls_towards_rest() {
1203        let mut app = test_app();
1204        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1205        let link = spawn_link(&mut app, ground, Vect::ZERO, prismatic(Vect::ZERO));
1206        app.world_mut().entity_mut(link).insert((
1207            MultibodyJointSprings::default().with(JointAxis::LinX, 50.0, 1.0),
1208            MultibodyJointDamping::splat(5.0),
1209        ));
1210        run(&mut app, 300);
1211
1212        let coord = joint_state(&app, link).coords[0];
1213        assert!(
1214            (coord - 1.0).abs() < 0.05,
1215            "the spring should reach its rest position: {coord}"
1216        );
1217        assert!((translation(&app, link).x - 1.0).abs() < 0.05);
1218        with_joints(&mut app, |bodies, joints| {
1219            let (mb, link_id) = joints.multibody(bodies, link).unwrap();
1220            let joint = mb.link(link_id).unwrap().joint();
1221            assert_eq!(joint.spring(0), (50.0, 1.0));
1222        });
1223
1224        app.world_mut()
1225            .entity_mut(link)
1226            .remove::<MultibodyJointSprings>();
1227        run(&mut app, 1);
1228        with_joints(&mut app, |bodies, joints| {
1229            let (mb, link_id) = joints.multibody(bodies, link).unwrap();
1230            assert_eq!(mb.link(link_id).unwrap().joint().spring(0), (0.0, 0.0));
1231        });
1232    }
1233
1234    #[test]
1235    fn multibody_inverse_kinematics_reaches_target() {
1236        let mut app = test_app();
1237        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1238        let link1 = spawn_link(&mut app, ground, Vect::X, revolute(Vect::ZERO));
1239        let link2 = spawn_link(&mut app, link1, Vect::X * 2.0, revolute(Vect::ZERO));
1240        run(&mut app, 1);
1241
1242        let target = Transform::from_xyz(1.0, 1.0, 0.0);
1243        let options = InverseKinematicsOption {
1244            constrained_axes: JointAxesMask::LIN_X | JointAxesMask::LIN_Y,
1245            max_iters: 50,
1246            ..Default::default()
1247        };
1248        let distance = |bodies: &RapierRigidBodySet| {
1249            let body = &bodies.bodies[bodies.entity2body()[&link2]];
1250            let pos = body.translation();
1251            (vect(pos.x, pos.y) - vect(1.0, 1.0)).length()
1252        };
1253
1254        with_joints_mut(&mut app, |bodies, joints| {
1255            let before = distance(bodies);
1256            let mut displacements = vec![];
1257            assert!(joints.multibody_inverse_kinematics(
1258                bodies,
1259                link2,
1260                target,
1261                &options,
1262                |_| true,
1263                &mut displacements,
1264            ));
1265            assert_eq!(displacements.len(), 2);
1266            // A reused buffer is reset before solving: its previous content doesn't matter.
1267            let mut reused = vec![10.0, -3.0, 7.0];
1268            assert!(joints.multibody_inverse_kinematics(
1269                bodies,
1270                link2,
1271                target,
1272                &options,
1273                |_| true,
1274                &mut reused,
1275            ));
1276            assert_eq!(reused, displacements);
1277            assert!(!joints.multibody_apply_displacements(bodies, link2, &[0.0]));
1278            assert!(joints.multibody_apply_displacements(bodies, link2, &displacements));
1279            assert!(joints.multibody_forward_kinematics(bodies, link2, false));
1280            let after = distance(bodies);
1281            assert!(
1282                after < before && after < 0.05,
1283                "IK should move the end effector to the target: {before} -> {after}"
1284            );
1285
1286            // Locking every joint prevents any motion.
1287            joints.multibody_inverse_kinematics(
1288                bodies,
1289                link2,
1290                Transform::from_xyz(-1.0, 1.0, 0.0),
1291                &options,
1292                |_| false,
1293                &mut displacements,
1294            );
1295            assert!(displacements.iter().all(|d| *d == 0.0));
1296            assert!(joints.multibody_body_jacobian(bodies, link2).is_some());
1297        });
1298    }
1299
1300    #[test]
1301    fn multibody_joint_state_is_written_back() {
1302        let mut app = test_app();
1303        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1304        let arm = spawn_link(&mut app, ground, Vect::X, revolute(Vect::ZERO));
1305        run(&mut app, 20);
1306
1307        let state = joint_state(&app, arm);
1308        let angle = state.coords[ROT_AXIS as usize];
1309        assert!(angle < -0.1, "the arm should swing down: {state:?}");
1310        assert!(state.velocities[ROT_AXIS as usize] < 0.0);
1311        assert_eq!(state.coords[0], 0.0);
1312
1313        let rotation = app.world().get::<Transform>(arm).unwrap().rotation;
1314        let (_, _, body_angle) = rotation.to_euler(EulerRot::XYZ);
1315        assert!(
1316            (body_angle - angle).abs() < 1.0e-3,
1317            "{body_angle} vs {angle}"
1318        );
1319        #[cfg(feature = "dim2")]
1320        assert!((state.rotation - angle).abs() < 1.0e-3);
1321        #[cfg(feature = "dim3")]
1322        assert!(state
1323            .rotation
1324            .abs_diff_eq(Quat::from_rotation_x(angle), 1.0e-3));
1325
1326        let from_context = with_joints(&mut app, |_, joints| {
1327            joints.multibody_joint_state(arm).unwrap()
1328        });
1329        assert_eq!(state, from_context);
1330        with_joints(&mut app, |bodies, joints| {
1331            let velocities = joints.multibody_generalized_velocity(bodies, arm).unwrap();
1332            assert_eq!(velocities, &[state.velocities[ROT_AXIS as usize]]);
1333            let raw = joints
1334                .multibody_joints
1335                .get(joints.entity2multibody_joint()[&arm]);
1336            let (mb, link_id) = raw.unwrap();
1337            assert_eq!(
1338                RapierContextJoints::entity_from_multibody_joint(mb.link(link_id).unwrap().joint()),
1339                arm
1340            );
1341        });
1342    }
1343
1344    #[test]
1345    fn multibody_topology_helpers() {
1346        let mut app = test_app();
1347        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1348        let other_ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 5.0);
1349        let b1 = spawn_link(&mut app, ground, Vect::X, revolute(Vect::ZERO));
1350        let b2 = spawn_link(&mut app, b1, Vect::X * 2.0, revolute(Vect::ZERO));
1351        let b3 = spawn_link(&mut app, b1, vect(2.0, -1.0), revolute(-Vect::Y));
1352        run(&mut app, 1);
1353
1354        with_joints(&mut app, |bodies, joints| {
1355            for entity in [ground, b1, b2, b3] {
1356                assert_eq!(joints.multibody_root(bodies, entity), Some(ground));
1357            }
1358            assert_eq!(joints.multibody_root(bodies, other_ground), None);
1359
1360            let links: Vec<_> = joints.multibody_links(bodies, b2).collect();
1361            assert_eq!(links.len(), 4);
1362            assert_eq!(links[0], ground);
1363            for entity in [b1, b2, b3] {
1364                assert!(links.contains(&entity));
1365            }
1366
1367            assert_eq!(joints.multibody_parent_link(bodies, ground), None);
1368            assert_eq!(joints.multibody_parent_link(bodies, b1), Some(ground));
1369            assert_eq!(joints.multibody_parent_link(bodies, b2), Some(b1));
1370            assert_eq!(joints.multibody_parent_link(bodies, b3), Some(b1));
1371
1372            assert_eq!(joints.multibody_joint_between(bodies, b1, b2), Some(b2));
1373            assert_eq!(joints.multibody_joint_between(bodies, b2, b1), Some(b2));
1374            assert_eq!(joints.multibody_joint_between(bodies, ground, b2), None);
1375
1376            let mut attached: Vec<_> = joints.attached_multibody_joints(bodies, b1).collect();
1377            attached.sort();
1378            let mut expected = vec![b1, b2, b3];
1379            expected.sort();
1380            assert_eq!(attached, expected);
1381        });
1382
1383        // Re-parenting moves the joint (and its descendants) to the new parent.
1384        app.world_mut()
1385            .get_mut::<MultibodyJoint>(b1)
1386            .unwrap()
1387            .parent = other_ground;
1388        run(&mut app, 1);
1389        with_joints(&mut app, |bodies, joints| {
1390            assert_eq!(joints.multibody_parent_link(bodies, b1), Some(other_ground));
1391            assert_eq!(joints.multibody_root(bodies, b2), Some(other_ground));
1392            assert_eq!(joints.multibody_root(bodies, ground), None);
1393        });
1394
1395        // Removing all the joints of a multibody.
1396        let joint_entities: Vec<_> = with_joints(&mut app, |bodies, joints| {
1397            joints.multibody_links(bodies, b1).skip(1).collect()
1398        });
1399        for entity in joint_entities {
1400            app.world_mut()
1401                .entity_mut(entity)
1402                .remove::<MultibodyJoint>();
1403        }
1404        run(&mut app, 1);
1405        with_joints(&mut app, |bodies, joints| {
1406            assert_eq!(joints.multibody_joints.multibodies().count(), 0);
1407            assert_eq!(joints.multibody_root(bodies, b1), None);
1408        });
1409    }
1410
1411    #[test]
1412    fn multibody_dof_couplings() {
1413        let mut app = test_app();
1414        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1415        let b1 = spawn_link(&mut app, ground, Vect::Y, prismatic(Vect::Y));
1416        let b2 = spawn_link(&mut app, ground, -Vect::Y, prismatic(-Vect::Y));
1417        app.world_mut()
1418            .entity_mut(b2)
1419            .insert(MultibodyJointCouplings(vec![MultibodyJointCoupling::new(
1420                JointAxis::LinX,
1421                b1,
1422                JointAxis::LinX,
1423                -1.0,
1424                0.0,
1425            )]));
1426        run(&mut app, 1);
1427        with_joints_mut(&mut app, |bodies, joints| {
1428            joints.multibody_joint_velocity_mut(bodies, b1).unwrap()[0] = 1.0;
1429        });
1430        run(&mut app, 30);
1431
1432        let x1 = joint_state(&app, b1).coords[0];
1433        let x2 = joint_state(&app, b2).coords[0];
1434        assert!(x1 > 0.1, "the first joint should move: {x1}");
1435        assert!(
1436            (x1 + x2).abs() < 1.0e-2,
1437            "the joints should be coupled: {x1} {x2}"
1438        );
1439        let num_couplings = |app: &mut App| {
1440            with_joints(app, |bodies, joints| {
1441                joints.multibody(bodies, b1).unwrap().0.couplings().len()
1442            })
1443        };
1444        assert_eq!(num_couplings(&mut app), 1);
1445
1446        // Removing the coupling keeps the joint state.
1447        app.world_mut()
1448            .entity_mut(b2)
1449            .remove::<MultibodyJointCouplings>();
1450        run(&mut app, 1);
1451        assert_eq!(num_couplings(&mut app), 0);
1452        let new_x2 = joint_state(&app, b2).coords[0];
1453        assert!((new_x2 - x2).abs() < 0.1, "{x2} -> {new_x2}");
1454        assert_eq!(
1455            with_joints(&mut app, |bodies, joints| joints.multibody_root(bodies, b2)),
1456            Some(ground)
1457        );
1458    }
1459
1460    #[test]
1461    fn multibody_couplings_and_self_contacts_follow_topology_changes() {
1462        let mut app = test_app();
1463        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1464        let b1 = spawn_link(&mut app, ground, Vect::Y, prismatic(Vect::Y));
1465        // `b2` declares its coupling and disabled self-contacts before being attached.
1466        let b2 = spawn_body(&mut app, RigidBody::Dynamic, 0.0, -1.0);
1467        let coupling = |coeff| {
1468            MultibodyJointCouplings(vec![MultibodyJointCoupling::new(
1469                JointAxis::LinX,
1470                b1,
1471                JointAxis::LinX,
1472                coeff,
1473                0.0,
1474            )])
1475        };
1476        app.world_mut().entity_mut(b2).insert((
1477            ColliderMassProperties::Mass(1.0),
1478            MultibodySelfContactsDisabled,
1479            coupling(-1.0),
1480        ));
1481        run(&mut app, 1);
1482
1483        let read = |app: &mut App| {
1484            with_joints(app, |bodies, joints| {
1485                let (mb, _) = joints.multibody(bodies, b1).unwrap();
1486                let coeffs: Vec<_> = mb.couplings().iter().map(|c| c.coeff).collect();
1487                (coeffs, mb.self_contacts_enabled())
1488            })
1489        };
1490        assert_eq!(read(&mut app), (vec![], true));
1491
1492        app.world_mut()
1493            .entity_mut(b2)
1494            .insert(MultibodyJoint::new(ground, prismatic(-Vect::Y)));
1495        run(&mut app, 1);
1496        assert_eq!(read(&mut app), (vec![-1.0], false));
1497
1498        // Editing the coupling replaces it.
1499        app.world_mut().entity_mut(b2).insert(coupling(2.0));
1500        run(&mut app, 1);
1501        assert_eq!(read(&mut app), (vec![2.0], false));
1502
1503        // Attaching another link keeps the coupling and the disabled self-contacts.
1504        let b3 = spawn_link(&mut app, b1, Vect::Y * 2.0, prismatic(Vect::Y));
1505        run(&mut app, 1);
1506        assert_eq!(read(&mut app), (vec![2.0], false));
1507        assert_eq!(
1508            with_joints(&mut app, |bodies, joints| joints.multibody_root(bodies, b3)),
1509            Some(ground)
1510        );
1511
1512        // Re-attaching the coupling source drops its coupling, which is then declared again.
1513        app.world_mut()
1514            .get_mut::<MultibodyJoint>(b1)
1515            .unwrap()
1516            .parent = b2;
1517        run(&mut app, 1);
1518        assert_eq!(read(&mut app), (vec![2.0], false));
1519        with_joints(&mut app, |bodies, joints| {
1520            let parent = |entity| {
1521                let (mb, link_id) = joints.multibody(bodies, entity).unwrap();
1522                let parent_id = mb.link(link_id).unwrap().parent_id().unwrap();
1523                mb.link(parent_id).unwrap().rigid_body_handle()
1524            };
1525            assert_eq!(parent(b1), bodies.entity2body()[&b2]);
1526        });
1527    }
1528
1529    #[test]
1530    fn multibody_self_contacts_armature_and_damping() {
1531        let mut app = test_app();
1532        let ground = spawn_body(&mut app, RigidBody::Fixed, 0.0, 0.0);
1533        let arm = spawn_link(&mut app, ground, Vect::X, revolute(Vect::ZERO));
1534        app.world_mut()
1535            .entity_mut(ground)
1536            .insert(MultibodySelfContactsDisabled);
1537        app.world_mut().entity_mut(arm).insert((
1538            MultibodyJointArmature::splat(0.5),
1539            MultibodyJointDamping::splat(2.0),
1540        ));
1541        run(&mut app, 1);
1542
1543        let read = |app: &mut App| {
1544            with_joints(app, |bodies, joints| {
1545                let (mb, _) = joints.multibody(bodies, arm).unwrap();
1546                let dof = joints.multibody_link_dofs(bodies, arm).unwrap().start;
1547                (
1548                    mb.self_contacts_enabled(),
1549                    mb.armature()[dof],
1550                    mb.damping()[dof],
1551                )
1552            })
1553        };
1554        assert_eq!(read(&mut app), (false, 0.5, 2.0));
1555
1556        app.world_mut()
1557            .entity_mut(ground)
1558            .remove::<MultibodySelfContactsDisabled>();
1559        app.world_mut()
1560            .entity_mut(arm)
1561            .remove::<(MultibodyJointArmature, MultibodyJointDamping)>();
1562        run(&mut app, 1);
1563        let default_damping = MultibodyJointDamping::default().0[ROT_AXIS as usize];
1564        assert_eq!(read(&mut app), (true, 0.0, default_damping));
1565    }
1566
1567    #[test]
1568    fn multibody_joint_properties_apply_to_late_joints() {
1569        let mut app = test_app();
1570        let ground = app
1571            .world_mut()
1572            .spawn(Transform::from_xyz(0.0, 0.0, 0.0))
1573            .id();
1574        let link = spawn_link(&mut app, ground, Vect::ZERO, prismatic(Vect::ZERO));
1575        app.world_mut().entity_mut(link).insert((
1576            KinematicMultibodyJoint,
1577            MultibodyJointSprings::default().with(JointAxis::LinX, 10.0, 2.0),
1578        ));
1579        run(&mut app, 2);
1580        assert!(app
1581            .world()
1582            .get::<RapierMultibodyJointHandle>(link)
1583            .is_none());
1584
1585        // The joint is only created once its parent gets a rigid-body.
1586        app.world_mut().entity_mut(ground).insert(RigidBody::Fixed);
1587        run(&mut app, 1);
1588        with_joints(&mut app, |bodies, joints| {
1589            let (mb, link_id) = joints.multibody(bodies, link).unwrap();
1590            let joint = mb.link(link_id).unwrap().joint();
1591            assert!(joint.kinematic);
1592            assert_eq!(joint.spring(0), (10.0, 2.0));
1593        });
1594    }
1595}