Skip to main content

rapier2d/dynamics/joint/multibody_joint/
multibody.rs

1use super::multibody_link::{MultibodyLink, MultibodyLinkVec};
2use super::multibody_workspace::MultibodyWorkspace;
3use crate::alloc_prelude::*;
4use crate::dynamics::integration_parameters::SpringCoefficients;
5use crate::dynamics::solver::{GenericJointConstraint, WritebackId};
6use crate::dynamics::{
7    IntegrationParameters, RigidBodyHandle, RigidBodySet, RigidBodyType, RigidBodyVelocity,
8};
9use crate::math::{
10    ANG_DIM, AngDim, AngVector, DIM, DVector, Dim, Jacobian, Pose, Real, SPATIAL_DIM,
11    SimdAngVector, Vector,
12};
13use crate::prelude::MultibodyJoint;
14#[cfg(feature = "dim3")]
15use crate::utils::mat_to_na;
16use crate::utils::{AngularInertiaOps, CrossProduct, CrossProductMatrix, IndexMut2, vect_to_na};
17use na::{
18    self, DMatrix, DVectorView, DVectorViewMut, Dyn, LU, OMatrix, SMatrix, SVector, StorageMut,
19};
20
21#[cfg(doc)]
22use crate::prelude::{GenericJoint, RigidBody};
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, Default)]
26struct Force {
27    linear: Vector,
28    angular: AngVector,
29}
30
31impl Force {
32    fn new(linear: Vector, angular: AngVector) -> Self {
33        Self { linear, angular }
34    }
35
36    fn as_vector(&self) -> &SVector<Real, SPATIAL_DIM> {
37        unsafe { core::mem::transmute(self) }
38    }
39}
40
41#[cfg(feature = "dim2")]
42fn concat_rb_mass_matrix(mass: Vector, inertia: Real) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
43    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
44    result[(0, 0)] = mass.x;
45    result[(1, 1)] = mass.y;
46    result[(2, 2)] = inertia;
47    result
48}
49
50#[cfg(feature = "dim3")]
51fn concat_rb_mass_matrix(
52    mass: Vector,
53    inertia: na::Matrix3<Real>,
54) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
55    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
56    result[(0, 0)] = mass.x;
57    result[(1, 1)] = mass.y;
58    result[(2, 2)] = mass.z;
59    result
60        .fixed_view_mut::<ANG_DIM, ANG_DIM>(DIM, DIM)
61        .copy_from(&inertia);
62    result
63}
64
65/// A holonomic coupling between two generalized coordinates of a single
66/// [`Multibody`], `q2 = coeff · q1 + offset`, enforced as a velocity-level
67/// equality constraint. This is how MuJoCo's `<equality><joint>` (a polynomial
68/// joint-to-joint coupling) is represented for the linear (first-order) case —
69/// e.g. the robotiq gripper's two driver joints moving together.
70#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
71#[derive(Copy, Clone, Debug)]
72pub struct MultibodyDofCoupling {
73    /// Internal id of the link owning the first joint.
74    pub link1: usize,
75    /// Local free-DoF index of the coupled DoF within `link1` (its position in
76    /// that link's slice of the generalized vectors).
77    pub dof1: usize,
78    /// Spatial-coordinate axis (`0..6`) of `link1`'s coupled DoF, used to read
79    /// its generalized position from the joint coords.
80    pub axis1: usize,
81    /// Internal id of the link owning the second joint.
82    pub link2: usize,
83    /// Local free-DoF index of the coupled DoF within `link2`.
84    pub dof2: usize,
85    /// Spatial-coordinate axis (`0..6`) of `link2`'s coupled DoF.
86    pub axis2: usize,
87    /// Linear coupling coefficient: the constraint is `q2 − coeff·q1 − offset = 0`.
88    pub coeff: Real,
89    /// Constant offset of the coupling.
90    pub offset: Real,
91}
92
93/// An articulated body simulated using the reduced-coordinates approach.
94#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
95#[derive(Clone, Debug)]
96pub struct Multibody {
97    // TODO: serialization: skip the workspace fields.
98    pub(crate) links: MultibodyLinkVec,
99    pub(crate) velocities: DVector,
100    pub(crate) damping: DVector,
101    /// Per-DoF reflected rotor inertia (matches MuJoCo’s concept of `armature`).
102    pub(crate) armature: DVector,
103    /// Per-DoF dry joint friction (matches MuJoCo’s `frictionloss`).
104    pub(crate) frictions: DVector,
105    pub(crate) accelerations: DVector,
106
107    body_jacobians: Vec<Jacobian<Real>>,
108    // NOTE: the mass matrices are dimensioned based on the non-kinematic degrees of
109    //       freedoms only. The `Self::augmented_mass_permutation` sequence can be used to
110    //       move dofs from/to a format that matches the augmented mass.
111    // TODO: use sparse matrices?
112    augmented_mass: DMatrix<Real>,
113    inv_augmented_mass: LU<Real, Dyn, Dyn>,
114    // The indexing sequence for moving all kinematics degrees of
115    // freedoms to the end of the generalized coordinates vector.
116    augmented_mass_indices: IndexSequence,
117
118    acc_augmented_mass: DMatrix<Real>,
119    acc_inv_augmented_mass: LU<Real, Dyn, Dyn>,
120
121    ndofs: usize,
122    pub(crate) root_is_dynamic: bool,
123    pub(crate) solver_id: u32,
124    self_contacts_enabled: bool,
125    /// Holonomic couplings between two of this multibody's generalized
126    /// coordinates (`q2 = coeff·q1 + offset`), e.g. MuJoCo's
127    /// `<equality><joint>`. Resolved as velocity constraints each step.
128    couplings: Vec<MultibodyDofCoupling>,
129
130    /*
131     * Workspaces.
132     */
133    workspace: MultibodyWorkspace,
134    coriolis_v: Vec<OMatrix<Real, Dim, Dyn>>,
135    coriolis_w: Vec<OMatrix<Real, AngDim, Dyn>>,
136    i_coriolis_dt: Jacobian<Real>,
137}
138impl Default for Multibody {
139    fn default() -> Self {
140        Multibody::new()
141    }
142}
143
144impl Multibody {
145    /// Creates a new multibody with no link.
146    pub fn new() -> Self {
147        Self::with_self_contacts(true)
148    }
149
150    pub(crate) fn with_self_contacts(self_contacts_enabled: bool) -> Self {
151        Multibody {
152            links: MultibodyLinkVec(Vec::new()),
153            velocities: DVector::zeros(0),
154            damping: DVector::zeros(0),
155            armature: DVector::zeros(0),
156            frictions: DVector::zeros(0),
157            accelerations: DVector::zeros(0),
158            body_jacobians: Vec::new(),
159            augmented_mass: DMatrix::zeros(0, 0),
160            inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
161            acc_augmented_mass: DMatrix::zeros(0, 0),
162            acc_inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
163            augmented_mass_indices: IndexSequence::new(),
164            ndofs: 0,
165            solver_id: 0,
166            workspace: MultibodyWorkspace::new(),
167            coriolis_v: Vec::new(),
168            coriolis_w: Vec::new(),
169            i_coriolis_dt: Jacobian::zeros(0),
170            root_is_dynamic: false,
171            self_contacts_enabled,
172            couplings: Vec::new(),
173            // solver_workspace: Some(SolverWorkspace::new()),
174        }
175    }
176
177    pub(crate) fn with_root(handle: RigidBodyHandle, self_contacts_enabled: bool) -> Self {
178        let mut mb = Multibody::with_self_contacts(self_contacts_enabled);
179        // NOTE: we have no way of knowing if the root in fixed at this point, so
180        //       we mark it as dynamic and will fix later with `Self::update_root_type`.
181        mb.root_is_dynamic = true;
182        let joint = MultibodyJoint::free(Pose::IDENTITY);
183        mb.add_link(None, joint, handle);
184        mb
185    }
186
187    pub(crate) fn remove_link(self, to_remove: usize, joint_only: bool) -> Vec<Multibody> {
188        let mut result = vec![];
189        let mut link2mb = vec![usize::MAX; self.links.len()];
190        let mut link_id2new_id = vec![usize::MAX; self.links.len()];
191        // Links whose joint is replaced by a fixed one: their DoFs no longer exist.
192        let mut joint_replaced = vec![false; self.links.len()];
193
194        // Split multibody and update the set of links and ndofs.
195        for (i, mut link) in self.links.0.into_iter().enumerate() {
196            let is_new_root = i == 0
197                || !joint_only && link.parent_internal_id == to_remove
198                || joint_only && i == to_remove;
199
200            if !joint_only && i == to_remove {
201                continue;
202            } else if is_new_root {
203                link2mb[i] = result.len();
204                result.push(Multibody::with_self_contacts(self.self_contacts_enabled));
205            } else {
206                link2mb[i] = link2mb[link.parent_internal_id]
207            }
208
209            let curr_mb = &mut result[link2mb[i]];
210            link_id2new_id[i] = curr_mb.links.len();
211
212            if is_new_root {
213                let joint = MultibodyJoint::fixed(*link.local_to_world());
214                link.joint = joint;
215                joint_replaced[i] = true;
216            }
217
218            curr_mb.ndofs += link.joint().ndofs();
219            curr_mb.links.push(link);
220        }
221
222        // Adjust all the internal ids, and copy the data from the
223        // previous multibody to the new one.
224        for mb in &mut result {
225            mb.grow_buffers(mb.ndofs, mb.links.len());
226            mb.workspace.resize(mb.links.len(), mb.ndofs);
227
228            let mut assembly_id = 0;
229            for (i, link) in mb.links.iter_mut().enumerate() {
230                let link_ndofs = link.joint().ndofs();
231                mb.velocities
232                    .rows_mut(assembly_id, link_ndofs)
233                    .copy_from(&self.velocities.rows(link.assembly_id, link_ndofs));
234                mb.damping
235                    .rows_mut(assembly_id, link_ndofs)
236                    .copy_from(&self.damping.rows(link.assembly_id, link_ndofs));
237                mb.armature
238                    .rows_mut(assembly_id, link_ndofs)
239                    .copy_from(&self.armature.rows(link.assembly_id, link_ndofs));
240                mb.frictions
241                    .rows_mut(assembly_id, link_ndofs)
242                    .copy_from(&self.frictions.rows(link.assembly_id, link_ndofs));
243                mb.accelerations
244                    .rows_mut(assembly_id, link_ndofs)
245                    .copy_from(&self.accelerations.rows(link.assembly_id, link_ndofs));
246
247                link.internal_id = i;
248                link.assembly_id = assembly_id;
249
250                // NOTE: for the root, the current`link.parent_internal_id` is invalid since that
251                //       parent lies in a different multibody now.
252                link.parent_internal_id = if i != 0 {
253                    link_id2new_id[link.parent_internal_id]
254                } else {
255                    0
256                };
257                assembly_id += link_ndofs;
258            }
259        }
260
261        // Keep the couplings whose DoFs still exist and ended up in the same multibody.
262        for coupling in &self.couplings {
263            let (l1, l2) = (coupling.link1, coupling.link2);
264            let kept = |l: usize| link_id2new_id[l] != usize::MAX && !joint_replaced[l];
265            if kept(l1) && kept(l2) && link2mb[l1] == link2mb[l2] {
266                result[link2mb[l1]].couplings.push(MultibodyDofCoupling {
267                    link1: link_id2new_id[l1],
268                    link2: link_id2new_id[l2],
269                    ..*coupling
270                });
271            }
272        }
273
274        result
275    }
276
277    pub(crate) fn append(&mut self, mut rhs: Multibody, parent: usize, joint: MultibodyJoint) {
278        let joint_ndofs = joint.ndofs();
279        let rhs_root_ndofs = rhs.links[0].joint.ndofs();
280        let ndofs_before_append = self.velocities.len();
281        let base_internal_id = self.links.len();
282        // Values for rhs will be copied into the buffers of `self` starting at this index.
283        let rhs_copy_shift = ndofs_before_append + joint_ndofs;
284        // Number of dofs to copy from rhs. The root’s dofs isn’t included because it will be
285        // replaced by `joint`.
286        let rhs_copy_ndofs = rhs.ndofs - rhs_root_ndofs;
287
288        // Adjust the ids of all the rhs links except the first one.
289        for link in &mut rhs.links.0[1..] {
290            link.assembly_id =
291                (link.assembly_id + ndofs_before_append + joint_ndofs) - rhs_root_ndofs;
292            link.internal_id += base_internal_id;
293            link.parent_internal_id += base_internal_id;
294        }
295
296        // Adjust the first link.
297        {
298            rhs.links[0].joint = joint;
299            rhs.links[0].assembly_id = ndofs_before_append;
300            rhs.links[0].internal_id = base_internal_id;
301            rhs.links[0].parent_internal_id = parent;
302        }
303
304        // Grow buffers then append data from rhs.
305        self.grow_buffers(rhs_copy_ndofs + joint_ndofs, rhs.links.len());
306
307        if rhs_copy_ndofs > 0 {
308            self.velocities
309                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
310                .copy_from(&rhs.velocities.rows(rhs_root_ndofs, rhs_copy_ndofs));
311            self.damping
312                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
313                .copy_from(&rhs.damping.rows(rhs_root_ndofs, rhs_copy_ndofs));
314            self.armature
315                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
316                .copy_from(&rhs.armature.rows(rhs_root_ndofs, rhs_copy_ndofs));
317            self.frictions
318                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
319                .copy_from(&rhs.frictions.rows(rhs_root_ndofs, rhs_copy_ndofs));
320            self.accelerations
321                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
322                .copy_from(&rhs.accelerations.rows(rhs_root_ndofs, rhs_copy_ndofs));
323        }
324
325        // Set the default damping for the new joint.
326        rhs.links[0]
327            .joint
328            .default_damping(&mut self.damping.rows_mut(ndofs_before_append, joint_ndofs));
329
330        self.links.append(&mut rhs.links);
331        self.ndofs = self.velocities.len();
332        self.workspace.resize(self.links.len(), self.ndofs);
333
334        // The rhs couplings follow its links, except the ones on its root whose joint was replaced.
335        self.couplings.extend(
336            rhs.couplings
337                .iter()
338                .filter(|c| c.link1 != 0 && c.link2 != 0)
339                .map(|c| MultibodyDofCoupling {
340                    link1: c.link1 + base_internal_id,
341                    link2: c.link2 + base_internal_id,
342                    ..*c
343                }),
344        );
345        // Self-contacts stay disabled for the links of a multibody that had them disabled.
346        self.self_contacts_enabled &= rhs.self_contacts_enabled;
347    }
348
349    /// Whether self-contacts are enabled on this multibody.
350    ///
351    /// If set to `false` no two link from this multibody can generate contacts, even
352    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
353    pub fn self_contacts_enabled(&self) -> bool {
354        self.self_contacts_enabled
355    }
356
357    /// Sets whether self-contacts are enabled on this multibody.
358    ///
359    /// If set to `false` no two link from this multibody can generate contacts, even
360    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
361    pub fn set_self_contacts_enabled(&mut self, enabled: bool) {
362        self.self_contacts_enabled = enabled;
363    }
364
365    /// The inverse augmented mass matrix of this multibody.
366    pub fn inv_augmented_mass(&self) -> &LU<Real, Dyn, Dyn> {
367        &self.inv_augmented_mass
368    }
369
370    /// The first link of this multibody.
371    #[inline]
372    pub fn root(&self) -> &MultibodyLink {
373        &self.links[0]
374    }
375
376    /// Mutable reference to the first link of this multibody.
377    #[inline]
378    pub fn root_mut(&mut self) -> &mut MultibodyLink {
379        &mut self.links[0]
380    }
381
382    /// Reference `i`-th multibody link of this multibody.
383    ///
384    /// Return `None` if there is less than `i + 1` multibody links.
385    #[inline]
386    pub fn link(&self, id: usize) -> Option<&MultibodyLink> {
387        self.links.get(id)
388    }
389
390    /// Mutable reference to the multibody link with the given id.
391    ///
392    /// Return `None` if the given id does not identifies a multibody link part of `self`.
393    #[inline]
394    pub fn link_mut(&mut self, id: usize) -> Option<&mut MultibodyLink> {
395        self.links.get_mut(id)
396    }
397
398    /// The number of links on this multibody.
399    pub fn num_links(&self) -> usize {
400        self.links.len()
401    }
402
403    /// Iterator through all the links of this multibody.
404    ///
405    /// All link are guaranteed to be yielded before its descendant.
406    pub fn links(&self) -> impl Iterator<Item = &MultibodyLink> {
407        self.links.iter()
408    }
409
410    /// Mutable iterator through all the links of this multibody.
411    ///
412    /// All link are guaranteed to be yielded before its descendant.
413    pub fn links_mut(&mut self) -> impl Iterator<Item = &mut MultibodyLink> {
414        self.links.iter_mut()
415    }
416
417    /// The vector of damping applied to this multibody.
418    #[inline]
419    pub fn damping(&self) -> &DVector {
420        &self.damping
421    }
422
423    /// Mutable vector of damping applied to this multibody.
424    #[inline]
425    pub fn damping_mut(&mut self) -> &mut DVector {
426        &mut self.damping
427    }
428
429    /// The vector of per-DoF armature (reflected rotor inertia) of this multibody: additional
430    /// inertia added directly to the mass matrix, simulating the joint's own weight distribution.
431    #[inline]
432    pub fn armature(&self) -> &DVector {
433        &self.armature
434    }
435
436    /// Mutable vector of per-DoF armature (reflected rotor inertia) of this
437    /// multibody.
438    #[inline]
439    pub fn armature_mut(&mut self) -> &mut DVector {
440        &mut self.armature
441    }
442
443    /// The vector of per-DoF dry joint friction (MuJoCo's `frictionloss`, in N
444    /// or N·m): the largest force friction may generate on that DoF.
445    #[inline]
446    pub fn frictions(&self) -> &DVector {
447        &self.frictions
448    }
449
450    /// Mutable vector of per-DoF dry joint friction of this multibody.
451    #[inline]
452    pub fn frictions_mut(&mut self) -> &mut DVector {
453        &mut self.frictions
454    }
455
456    pub(crate) fn add_link(
457        &mut self,
458        parent: Option<usize>, // TODO: should be a RigidBodyHandle?
459        dof: MultibodyJoint,
460        body: RigidBodyHandle,
461    ) -> &mut MultibodyLink {
462        assert!(
463            parent.is_none() || !self.links.is_empty(),
464            "Multibody::build_body: invalid parent id."
465        );
466
467        /*
468         * Compute the indices.
469         */
470        let assembly_id = self.velocities.len();
471        let internal_id = self.links.len();
472
473        /*
474         * Grow the buffers.
475         */
476        let ndofs = dof.ndofs();
477        self.grow_buffers(ndofs, 1);
478        self.ndofs += ndofs;
479
480        /*
481         * Setup default damping.
482         */
483        dof.default_damping(&mut self.damping.rows_mut(assembly_id, ndofs));
484
485        /*
486         * Create the multibody.
487         */
488        let local_to_parent = dof.body_to_parent();
489        let local_to_world;
490
491        let parent_internal_id;
492        if let Some(parent) = parent {
493            parent_internal_id = parent;
494            let parent_link = &mut self.links[parent_internal_id];
495            local_to_world = parent_link.local_to_world * local_to_parent;
496        } else {
497            parent_internal_id = 0;
498            local_to_world = local_to_parent;
499        }
500
501        let rb = MultibodyLink::new(
502            body,
503            internal_id,
504            assembly_id,
505            parent_internal_id,
506            dof,
507            local_to_world,
508            local_to_parent,
509        );
510
511        self.links.push(rb);
512        self.workspace.resize(self.links.len(), self.ndofs);
513
514        &mut self.links[internal_id]
515    }
516
517    fn grow_buffers(&mut self, ndofs: usize, num_jacobians: usize) {
518        let len = self.velocities.len();
519        self.velocities.resize_vertically_mut(len + ndofs, 0.0);
520        self.damping.resize_vertically_mut(len + ndofs, 0.0);
521        self.armature.resize_vertically_mut(len + ndofs, 0.0);
522        self.frictions.resize_vertically_mut(len + ndofs, 0.0);
523        self.accelerations.resize_vertically_mut(len + ndofs, 0.0);
524        self.body_jacobians
525            .extend((0..num_jacobians).map(|_| Jacobian::zeros(0)));
526    }
527
528    pub(crate) fn update_acceleration(&mut self, dt: Real, bodies: &RigidBodySet) {
529        if self.ndofs == 0 {
530            return; // Nothing to do.
531        }
532
533        self.accelerations.fill(0.0);
534
535        // If there is significant movement, the semi-implicit coriolis term
536        // occasionally cause instabilities due to the matrix being near singular.
537        // To prevent that, we enable a check that verifies the result and fallbacks
538        // to the explicit term if needed
539        let check_implicit_coriolis_divergence = dt * self.velocities.amax() >= 1.0e-3;
540        // Generalized forces that derive from positions or external inputs
541        // (gravity, user forces, spring position terms).
542        let mut pos_forces = DVector::zeros(if check_implicit_coriolis_divergence {
543            self.ndofs
544        } else {
545            0
546        });
547
548        // Eqn 42 to 45
549        for i in 0..self.links.len() {
550            let link = &self.links[i];
551            let rb = &bodies[link.rigid_body];
552
553            let mut acc = RigidBodyVelocity::zero();
554
555            if i != 0 {
556                let parent_id = link.parent_internal_id;
557                let parent_link = &self.links[parent_id];
558                let parent_rb = &bodies[parent_link.rigid_body];
559
560                acc += self.workspace.accs[parent_id];
561                // The 2.0 originates from the two identical terms of Jdot (the terms become
562                // identical once they are multiplied by the generalized velocities).
563                acc.linvel += 2.0 * parent_rb.vels.angvel.gcross(link.joint_velocity.linvel);
564                #[cfg(feature = "dim3")]
565                {
566                    acc.angvel += parent_rb.vels.angvel.cross(link.joint_velocity.angvel);
567                }
568
569                acc.linvel += parent_rb
570                    .vels
571                    .angvel
572                    .gcross(parent_rb.vels.angvel.gcross(link.shift02));
573                acc.linvel += self.workspace.accs[parent_id].angvel.gcross(link.shift02);
574            }
575
576            acc.linvel += rb.vels.angvel.gcross(rb.vels.angvel.gcross(link.shift23));
577            acc.linvel += acc.angvel.gcross(link.shift23);
578
579            self.workspace.accs[i] = acc;
580
581            // TODO: should gyroscopic forces already be computed by the rigid-body itself
582            //       (at the same time that we add the gravity force)?
583            let gyroscopic;
584            let rb_inertia = rb.mprops.effective_angular_inertia();
585            let rb_mass = rb.mprops.effective_mass();
586
587            #[cfg(feature = "dim3")]
588            {
589                let angvel = rb.vels.angvel;
590                let inertia_times_angvel = rb_inertia * angvel;
591                gyroscopic = angvel.cross(inertia_times_angvel);
592            }
593            #[cfg(feature = "dim2")]
594            {
595                gyroscopic = 0.0;
596            }
597
598            let external_forces = Force::new(
599                rb.forces.force - rb_mass * acc.linvel,
600                rb.forces.torque - gyroscopic - rb_inertia * acc.angvel,
601            );
602            self.accelerations.gemv_tr(
603                1.0,
604                &self.body_jacobians[i],
605                external_forces.as_vector(),
606                1.0,
607            );
608
609            if check_implicit_coriolis_divergence {
610                let applied_forces = Force::new(rb.forces.force, rb.forces.torque);
611                pos_forces.gemv_tr(
612                    1.0,
613                    &self.body_jacobians[i],
614                    applied_forces.as_vector(),
615                    1.0,
616                );
617            }
618        }
619
620        self.accelerations
621            .cmpy(-1.0, &self.damping, &self.velocities, 1.0);
622
623        // Implicit joint springs: backward-Euler at `q⁺ = q + dt·v⁺` gives `-k·(q − rest) − k·dt·v⁺`,
624        // with the `v⁺` part implicit via the `dt²·k` mass-matrix diagonal (see `update_mass_matrix`).
625        // Consistency requires BOTH `-k·(q − rest)` and `-k·dt·v` here, else the spring is only semi-implicit.
626        for li in 0..self.links.len() {
627            let mut idx = self.links[li].assembly_id;
628            let locked = self.links[li].joint.data.locked_axes.bits();
629            for a in 0..SPATIAL_DIM {
630                if (locked >> a) & 1 == 0 {
631                    let k = self.links[li].joint.spring_stiffness[a];
632                    if k != 0.0 {
633                        let q = self.links[li].joint.coords[a];
634                        let rest = self.links[li].joint.spring_ref[a];
635                        let spring_pos_force = -k * (q - rest);
636                        self.accelerations[idx] += spring_pos_force - k * dt * self.velocities[idx];
637                        if check_implicit_coriolis_divergence {
638                            pos_forces[idx] += spring_pos_force;
639                        }
640                    }
641                    idx += 1;
642                }
643            }
644        }
645
646        // Snapshot the full generalized forces: the energy guard may need them
647        // for a re-solve with the plain mass matrix.
648        let gen_forces = if check_implicit_coriolis_divergence {
649            self.accelerations.clone()
650        } else {
651            DVector::zeros(0)
652        };
653
654        self.augmented_mass_indices
655            .with_rearranged_rows_mut(&mut self.accelerations, |accs| {
656                self.acc_inv_augmented_mass.solve_mut(accs);
657            });
658
659        if check_implicit_coriolis_divergence {
660            self.free_velocity_energy_guard(dt, &gen_forces, &pos_forces);
661        }
662    }
663
664    // If the semi-implicit coriolis solve introduces more energy than the external
665    // forces’ work, we recalculate without the coriolis term in the mass matrix.
666    //
667    // This operates by calculating the effective kinematic energy added by the
668    // external forces after the solve with the semi-implicit mass matrix, and
669    // compare it with the force’s work. If the energy delta exceeds a threshold
670    // (indicating undesired/unrealistic energy injection), we fall back to re-solving
671    // the forces effect but using only the mass matrix without the implicit coriolis
672    // terms.
673    fn free_velocity_energy_guard(&mut self, dt: Real, gen_forces: &DVector, pos_forces: &DVector) {
674        let eff_dim = self.augmented_mass_indices.dim_after_removal(self.ndofs);
675        if eff_dim == 0 {
676            return;
677        }
678
679        // Move all quantities to the kinematic-reduced ordering of the
680        // factorized matrices (no-op when there is no kinematic dof).
681        let mut v = self.velocities.clone();
682        let mut f = gen_forces.clone();
683        let mut f_pos = pos_forces.clone();
684        self.augmented_mass_indices.rearrange_rows(&mut v, true);
685        self.augmented_mass_indices.rearrange_rows(&mut f, true);
686        self.augmented_mass_indices.rearrange_rows(&mut f_pos, true);
687
688        let m = self.augmented_mass.view((0, 0), (eff_dim, eff_dim));
689        let v = v.rows(0, eff_dim);
690        let f = f.rows(0, eff_dim);
691        let f_pos = f_pos.rows(0, eff_dim);
692
693        let energy_delta_minus_work = |accelerations: &DVector| -> Option<Real> {
694            let mut a = accelerations.clone();
695            self.augmented_mass_indices.rearrange_rows(&mut a, true);
696            let a = a.rows(0, eff_dim).into_owned();
697            if !a.iter().all(|x| x.is_finite()) {
698                return None;
699            }
700            let m_a = m * &a;
701            let energy_delta = dt * v.dot(&m_a) + 0.5 * dt * dt * a.dot(&m_a);
702            let work = dt * v.dot(&f) + 0.5 * dt * dt * a.dot(&f_pos);
703            // Margin: a small fraction of the current kinetic energy to absorb
704            // discretization error on healthy steps.
705            let kinetic_energy = 0.5 * v.dot(&(m * v));
706            let energy_margin = 1.0e-2 * kinetic_energy + 1.0e-8;
707            Some(energy_delta - work - energy_margin)
708        };
709
710        match energy_delta_minus_work(&self.accelerations) {
711            Some(excess) if excess <= 0.0 => (), // Implicit update didn’t introduce too much extra energy.
712            _ => {
713                // The implicit solve injected energy (or produced non-finite
714                // values): fall back to the plain mass matrix.
715                self.accelerations.copy_from(gen_forces);
716                self.augmented_mass_indices.with_rearranged_rows_mut(
717                    &mut self.accelerations,
718                    |accs| {
719                        self.inv_augmented_mass.solve_mut(accs);
720                    },
721                );
722
723                // Last-chance check: if all else fails, just clear the accelerations vector
724                // instead of breaking the simulation. Hopefully it will get back to a
725                // sane result in a close timestep.
726                if !self.accelerations.iter().all(|x| x.is_finite()) {
727                    self.accelerations.fill(0.0);
728                }
729            }
730        }
731    }
732
733    /// Computes the constant terms of the dynamics.
734    #[profiling::function]
735    pub(crate) fn update_velocities(&mut self, bodies: &mut RigidBodySet) {
736        /*
737         * Compute velocities.
738         * NOTE: this is needed for kinematic bodies too.
739         */
740        let link = &mut self.links[0];
741        let joint_velocity = link
742            .joint
743            .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
744
745        link.joint_velocity = joint_velocity;
746        bodies.index_mut_internal(link.rigid_body).vels = link.joint_velocity;
747
748        for i in 1..self.links.len() {
749            let (link, parent_link) = self.links.get_mut_with_parent(i);
750            let rb = &bodies[link.rigid_body];
751            let parent_rb = &bodies[parent_link.rigid_body];
752
753            let joint_velocity = link
754                .joint
755                .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
756            link.joint_velocity = joint_velocity.transformed(
757                &(parent_link.local_to_world.rotation * link.joint.data.local_frame1.rotation),
758            );
759            let mut new_rb_vels = parent_rb.vels + link.joint_velocity;
760            let shift = rb.mprops.world_com - parent_rb.mprops.world_com;
761            new_rb_vels.linvel += parent_rb.vels.angvel.gcross(shift);
762            new_rb_vels.linvel += link.joint_velocity.angvel.gcross(link.shift23);
763
764            bodies.index_mut_internal(link.rigid_body).vels = new_rb_vels;
765        }
766    }
767
768    fn update_body_jacobians(&mut self) {
769        for i in 0..self.links.len() {
770            let link = &self.links[i];
771
772            if self.body_jacobians[i].ncols() != self.ndofs {
773                // TODO: use a resize instead.
774                self.body_jacobians[i] = Jacobian::zeros(self.ndofs);
775            }
776
777            let parent_to_world;
778
779            if i != 0 {
780                let parent_id = link.parent_internal_id;
781                let parent_link = &self.links[parent_id];
782                parent_to_world = parent_link.local_to_world;
783
784                let (link_j, parent_j) = self.body_jacobians.index_mut_const(i, parent_id);
785                link_j.copy_from(parent_j);
786
787                {
788                    let mut link_j_v = link_j.fixed_rows_mut::<DIM>(0);
789                    let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
790
791                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
792                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
793                }
794            } else {
795                self.body_jacobians[i].fill(0.0);
796                parent_to_world = Pose::IDENTITY;
797            }
798
799            let ndofs = link.joint.ndofs();
800            let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
801            let mut link_joint_j = tmp.columns_mut(0, ndofs);
802            let mut link_j_part = self.body_jacobians[i].columns_mut(link.assembly_id, ndofs);
803            link.joint.jacobian(
804                &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
805                &mut link_joint_j,
806            );
807            link_j_part += link_joint_j;
808
809            {
810                let link_j = &mut self.body_jacobians[i];
811                let (mut link_j_v, link_j_w) =
812                    link_j.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
813                let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
814                link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
815            }
816        }
817    }
818
819    pub(crate) fn update_mass_matrix(&mut self, dt: Real, bodies: &RigidBodySet) {
820        if self.ndofs == 0 {
821            return; // Nothing to do.
822        }
823
824        if self.augmented_mass.ncols() != self.ndofs {
825            // TODO: do a resize instead of a full reallocation.
826            self.augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
827            self.acc_augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
828        } else {
829            self.augmented_mass.fill(0.0);
830            self.acc_augmented_mass.fill(0.0);
831        }
832
833        self.augmented_mass_indices.clear();
834
835        // Resize coriolis workspaces if the link count or number of DOFs change.
836        let coriolis_ndofs = self.coriolis_v.first().map(|m| m.ncols());
837        if self.coriolis_v.len() != self.links.len() || coriolis_ndofs != Some(self.ndofs) {
838            self.coriolis_v = vec![OMatrix::<Real, Dim, Dyn>::zeros(self.ndofs); self.links.len()];
839            self.coriolis_w =
840                vec![OMatrix::<Real, AngDim, Dyn>::zeros(self.ndofs); self.links.len()];
841            self.i_coriolis_dt = Jacobian::zeros(self.ndofs);
842        }
843
844        let mut curr_assembly_id = 0;
845
846        for i in 0..self.links.len() {
847            let link = &self.links[i];
848            let rb = &bodies[link.rigid_body];
849            let rb_mass = rb.mprops.effective_mass();
850            let rb_inertia = rb.mprops.effective_angular_inertia().into_matrix();
851            let body_jacobian = &self.body_jacobians[i];
852
853            // NOTE: the mass matrix index reordering operates on the assumption that the assembly
854            //       ids are traversed in order. This assert is here to ensure the assumption always
855            //       hold.
856            assert_eq!(
857                curr_assembly_id, link.assembly_id,
858                "Internal error: contiguity assumption on assembly_id does not hold."
859            );
860            curr_assembly_id += link.joint.ndofs();
861
862            if link.joint.kinematic {
863                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
864                    self.augmented_mass_indices.remove(k);
865                }
866            } else {
867                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
868                    self.augmented_mass_indices.keep(k);
869                }
870            }
871
872            #[allow(unused_mut)] // mut is needed for 3D but not for 2D.
873            let mut augmented_inertia = rb_inertia;
874
875            #[cfg(feature = "dim3")]
876            {
877                // Derivative of gyroscopic forces.
878                let gyroscopic_matrix = rb.vels.angvel.gcross_matrix() * rb_inertia
879                    - (rb_inertia * rb.vels.angvel).gcross_matrix();
880
881                augmented_inertia += gyroscopic_matrix * dt;
882            }
883
884            // TODO: optimize that (knowing the structure of the augmented inertia matrix).
885            // TODO: this could be better optimized in 2D.
886            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
887            let rb_mass_matrix_wo_gyro = concat_rb_mass_matrix(rb_mass, rb_inertia.into());
888            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
889            let rb_mass_matrix = concat_rb_mass_matrix(rb_mass, augmented_inertia.into());
890            self.augmented_mass
891                .quadform(1.0, &rb_mass_matrix_wo_gyro, body_jacobian, 1.0);
892            self.acc_augmented_mass
893                .quadform(1.0, &rb_mass_matrix, body_jacobian, 1.0);
894
895            /*
896             *
897             * Coriolis matrix.
898             *
899             */
900            let rb_j = &self.body_jacobians[i];
901            let rb_j_w = rb_j.fixed_rows::<ANG_DIM>(DIM);
902
903            let ndofs = link.joint.ndofs();
904
905            if i != 0 {
906                let parent_id = link.parent_internal_id;
907                let parent_link = &self.links[parent_id];
908                let parent_rb = &bodies[parent_link.rigid_body];
909                let parent_j = &self.body_jacobians[parent_id];
910                let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
911                let parent_w = SimdAngVector::<Real>::from(parent_rb.vels.angvel).gcross_matrix();
912
913                let (coriolis_v, parent_coriolis_v) = self.coriolis_v.index_mut2(i, parent_id);
914                let (coriolis_w, parent_coriolis_w) = self.coriolis_w.index_mut2(i, parent_id);
915
916                coriolis_v.copy_from(parent_coriolis_v);
917                coriolis_w.copy_from(parent_coriolis_w);
918
919                // [c1 - c0].gcross() * (JDot + JDot/u * qdot)"
920                let shift_cross_tr = vect_to_na(link.shift02).gcross_matrix_tr();
921                coriolis_v.gemm(1.0, &shift_cross_tr, parent_coriolis_w, 1.0);
922
923                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
924                let dvel_cross = vect_to_na(
925                    rb.vels.angvel.gcross(link.shift02) + 2.0 * link.joint_velocity.linvel,
926                )
927                .gcross_matrix_tr();
928                coriolis_v.gemm(1.0, &dvel_cross, &parent_j_w, 1.0);
929
930                // JDot/u * qdot
931                coriolis_v.gemm(
932                    1.0,
933                    &vect_to_na(link.joint_velocity.linvel).gcross_matrix_tr(),
934                    &parent_j_w,
935                    1.0,
936                );
937                coriolis_v.gemm(1.0, &(parent_w * shift_cross_tr), &parent_j_w, 1.0);
938
939                #[cfg(feature = "dim3")]
940                {
941                    let vel_wrt_joint_w = vect_to_na(link.joint_velocity.angvel).gcross_matrix();
942                    coriolis_w.gemm(-1.0, &vel_wrt_joint_w, &parent_j_w, 1.0);
943                }
944
945                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
946                if !link.joint.kinematic {
947                    let mut coriolis_v_part = coriolis_v.columns_mut(link.assembly_id, ndofs);
948
949                    let mut tmp1 = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
950                    let mut rb_joint_j = tmp1.columns_mut(0, ndofs);
951                    link.joint.jacobian(
952                        &(parent_link.local_to_world.rotation
953                            * link.joint.data.local_frame1.rotation),
954                        &mut rb_joint_j,
955                    );
956
957                    let rb_joint_j_v = rb_joint_j.fixed_rows::<DIM>(0);
958                    coriolis_v_part.gemm(2.0, &parent_w, &rb_joint_j_v, 1.0);
959
960                    #[cfg(feature = "dim3")]
961                    {
962                        let rb_joint_j_w = rb_joint_j.fixed_rows::<ANG_DIM>(DIM);
963                        let mut coriolis_w_part = coriolis_w.columns_mut(link.assembly_id, ndofs);
964                        coriolis_w_part.gemm(1.0, &parent_w, &rb_joint_j_w, 1.0);
965                    }
966                }
967            } else {
968                self.coriolis_v[i].fill(0.0);
969                self.coriolis_w[i].fill(0.0);
970            }
971
972            let coriolis_v = &mut self.coriolis_v[i];
973            let coriolis_w = &mut self.coriolis_w[i];
974
975            {
976                // [c3 - c2].gcross() * (JDot + JDot/u * qdot)
977                let shift_cross_tr = vect_to_na(link.shift23).gcross_matrix_tr();
978                coriolis_v.gemm(1.0, &shift_cross_tr, coriolis_w, 1.0);
979
980                // JDot
981                let dvel_cross = vect_to_na(rb.vels.angvel.gcross(link.shift23)).gcross_matrix_tr();
982                coriolis_v.gemm(1.0, &dvel_cross, &rb_j_w, 1.0);
983
984                // JDot/u * qdot
985                coriolis_v.gemm(
986                    1.0,
987                    &(SimdAngVector::<Real>::from(rb.vels.angvel).gcross_matrix() * shift_cross_tr),
988                    &rb_j_w,
989                    1.0,
990                );
991            }
992
993            let coriolis_v = &mut self.coriolis_v[i];
994            let coriolis_w = &mut self.coriolis_w[i];
995
996            /*
997             * Meld with the mass matrix.
998             */
999            {
1000                let mut i_coriolis_dt_v = self.i_coriolis_dt.fixed_rows_mut::<DIM>(0);
1001                i_coriolis_dt_v.copy_from(coriolis_v);
1002                let rb_mass_dt = vect_to_na(rb_mass * dt);
1003                i_coriolis_dt_v
1004                    .column_iter_mut()
1005                    .for_each(|mut c| c.component_mul_assign(&rb_mass_dt));
1006            }
1007
1008            #[cfg(feature = "dim2")]
1009            {
1010                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
1011                // NOTE: this is just an axpy, but on row columns.
1012                i_coriolis_dt_w.zip_apply(coriolis_w, |o, x| *o = x * dt * rb_inertia);
1013            }
1014            #[cfg(feature = "dim3")]
1015            {
1016                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
1017                i_coriolis_dt_w.gemm(dt, &mat_to_na(rb_inertia), coriolis_w, 0.0);
1018            }
1019
1020            self.acc_augmented_mass
1021                .gemm_tr(1.0, rb_j, &self.i_coriolis_dt, 1.0);
1022        }
1023
1024        /*
1025         * Damping and armature.
1026         *
1027         * Damping is a velocity-proportional force made implicit, so it adds
1028         * `dt · d` to the mass-matrix diagonal. Armature is a "reflected
1029         * rotor inertia" (additional inertia to account for the joint’s
1030         * mass and geometry itself): it adds to the diagonal directly.
1031         */
1032        for i in 0..self.ndofs {
1033            let diag = self.damping[i] * dt + self.armature[i];
1034            self.acc_augmented_mass[(i, i)] += diag;
1035            self.augmented_mass[(i, i)] += diag;
1036        }
1037
1038        // Implicit joint springs: `dt²·k` on the mass-matrix diagonal (force term in `update_acceleration`)
1039        // keeps a stiff spring on a low-inertia link stable where an explicit position motor injects energy.
1040        // The spring lives on the link's `MultibodyJoint` so it travels with the link through topology changes.
1041        let dt2 = dt * dt;
1042        for li in 0..self.links.len() {
1043            let mut idx = self.links[li].assembly_id;
1044            let locked = self.links[li].joint.data.locked_axes.bits();
1045            for a in 0..SPATIAL_DIM {
1046                if (locked >> a) & 1 == 0 {
1047                    let k = self.links[li].joint.spring_stiffness[a];
1048                    if k != 0.0 {
1049                        let d = k * dt2;
1050                        self.acc_augmented_mass[(idx, idx)] += d;
1051                        self.augmented_mass[(idx, idx)] += d;
1052                    }
1053                    idx += 1;
1054                }
1055            }
1056        }
1057
1058        let effective_dim = self
1059            .augmented_mass_indices
1060            .dim_after_removal(self.acc_augmented_mass.nrows());
1061
1062        // PERF: since we clone the matrix anyway for LU, should be directly output
1063        //       a new matrix instead of applying permutations?
1064        self.augmented_mass_indices
1065            .rearrange_columns(&mut self.acc_augmented_mass, true);
1066        self.augmented_mass_indices
1067            .rearrange_columns(&mut self.augmented_mass, true);
1068
1069        self.augmented_mass_indices
1070            .rearrange_rows(&mut self.acc_augmented_mass, true);
1071        self.augmented_mass_indices
1072            .rearrange_rows(&mut self.augmented_mass, true);
1073
1074        // TODO: avoid allocation inside LU at each timestep.
1075        self.acc_inv_augmented_mass = LU::new(
1076            self.acc_augmented_mass
1077                .view((0, 0), (effective_dim, effective_dim))
1078                .into_owned(),
1079        );
1080        self.inv_augmented_mass = LU::new(
1081            self.augmented_mass
1082                .view((0, 0), (effective_dim, effective_dim))
1083                .into_owned(),
1084        );
1085    }
1086
1087    /// Per-DoF inverse joint-space inertia `diag(M⁻¹)` at the current configuration, where `M`
1088    /// includes armature but excludes joint damping and springs — MuJoCo's `dof_invweight0`: the
1089    /// apparent inverse inertia at each DoF with all other DoFs free, including the articulated
1090    /// coupling. Re-runs forward kinematics and reassembles the mass matrix, so intended for
1091    /// occasional use (e.g. sizing `<joint springdamper>` springs at load time), not every step.
1092    pub fn dof_inverse_inertia(&mut self, bodies: &RigidBodySet) -> DVector {
1093        // Resolve the root joint type (a fixed base may still be a 6-DoF free root pre-collapse)
1094        // so `ndofs` is final, then assemble `M`. `dt = 0` drops the `dt·damping` and
1095        // `dt²·stiffness` diagonal terms, leaving exactly `M + armature`.
1096        self.forward_kinematics(bodies, false);
1097        self.update_mass_matrix(0.0, bodies);
1098
1099        let n = self.ndofs;
1100        let mut out = DVector::zeros(n);
1101        if n == 0 {
1102            return out;
1103        }
1104        // `(M⁻¹)[i, i]` for each DoF: solve `M x = e_i` and read `x[i]`. The `inv_augmented_mass`
1105        // factorization lives in the kinematic-reduced ordering, so route the unit vector through
1106        // the same rearrangement the solver uses.
1107        let mut e = DVector::zeros(n);
1108        for i in 0..n {
1109            e.fill(0.0);
1110            e[i] = 1.0;
1111            self.augmented_mass_indices
1112                .with_rearranged_rows_mut(&mut e, |b| {
1113                    self.inv_augmented_mass.solve_mut(b);
1114                });
1115            out[i] = e[i];
1116        }
1117        out
1118    }
1119
1120    /// Adds a holonomic coupling between two of this multibody's generalized
1121    /// coordinates (`q2 = coeff·q1 + offset`), enforced as a velocity-level
1122    /// equality constraint each step. See [`MultibodyDofCoupling`].
1123    pub fn add_dof_coupling(&mut self, coupling: MultibodyDofCoupling) {
1124        self.couplings.push(coupling);
1125    }
1126
1127    /// The DoF couplings declared on this multibody.
1128    ///
1129    /// Couplings follow the multibody's topology changes (link insertions, merges and splits);
1130    /// a coupling is dropped once one of its DoFs no longer exists, or when its two DoFs end up
1131    /// in different multibodies.
1132    pub fn couplings(&self) -> &[MultibodyDofCoupling] {
1133        &self.couplings
1134    }
1135
1136    /// Removes the `i`-th DoF coupling (in the order of [`Self::couplings`]) and returns it.
1137    ///
1138    /// Returns `None` if there is no such coupling. The couplings after it shift down by one.
1139    pub fn remove_dof_coupling(&mut self, i: usize) -> Option<MultibodyDofCoupling> {
1140        (i < self.couplings.len()).then(|| self.couplings.remove(i))
1141    }
1142
1143    /// Keeps only the DoF couplings for which `f` returns `true`.
1144    pub fn retain_dof_couplings(&mut self, f: impl FnMut(&MultibodyDofCoupling) -> bool) {
1145        self.couplings.retain(f);
1146    }
1147
1148    /// Removes all the DoF couplings of this multibody.
1149    pub fn clear_dof_couplings(&mut self) {
1150        self.couplings.clear();
1151    }
1152
1153    /// The number of dry-friction rows `link_id`'s joint will emit: one per
1154    /// free DoF whose `frictionloss` entry is non-zero.
1155    pub(crate) fn num_friction_constraints(&self, link_id: usize) -> usize {
1156        let Some(link) = self.link(link_id) else {
1157            return 0;
1158        };
1159        let ndofs = link.joint().ndofs();
1160        (0..ndofs)
1161            .filter(|i| self.frictions[link.assembly_id + i] > 0.0)
1162            .count()
1163    }
1164
1165    /// Number of coupling constraints "owned" by `owner_link` — couplings whose first joint
1166    /// (`link1`) is that link. Each coupling is generated once, by `link1` (which always has a
1167    /// free DoF and so is an active link in the solver island, unlike a possibly-fixed root).
1168    pub(crate) fn num_couplings_owned_by(&self, owner_link: usize) -> usize {
1169        self.couplings
1170            .iter()
1171            .filter(|c| c.link1 == owner_link)
1172            .count()
1173    }
1174
1175    /// Generates the velocity constraints for the DoF couplings owned by `owner_link` into
1176    /// `out[..]`: each coupling `q2 = coeff·q1 + offset` is one bilateral constraint with jacobian
1177    /// `J = e_{q2} − coeff·e_{q1}` and a rhs pulling the position drift back to zero.
1178    pub(crate) fn coupling_velocity_constraints(
1179        &self,
1180        owner_link: usize,
1181        params: &IntegrationParameters,
1182        mut j_id: usize,
1183        jacobians: &mut DVector,
1184        out: &mut [GenericJointConstraint],
1185    ) -> usize {
1186        let ndofs = self.ndofs;
1187        let erp_inv_dt = SpringCoefficients::<Real>::joint_defaults().erp_inv_dt(params.dt);
1188
1189        let mut i = 0;
1190        for c in self.couplings.iter().filter(|c| c.link1 == owner_link) {
1191            let g1 = self.links[c.link1].assembly_id + c.dof1;
1192            let g2 = self.links[c.link2].assembly_id + c.dof2;
1193            let q1 = self.links[c.link1].joint().coords()[c.axis1];
1194            let q2 = self.links[c.link2].joint().coords()[c.axis2];
1195
1196            // Jacobian J = e_{g2} − coeff·e_{g1}, then WJ = M⁻¹ J.
1197            jacobians.rows_mut(j_id, ndofs * 2).fill(0.0);
1198            jacobians[j_id + g2] += 1.0;
1199            jacobians[j_id + g1] -= c.coeff;
1200            for k in 0..ndofs {
1201                jacobians[j_id + ndofs + k] = jacobians[j_id + k];
1202            }
1203            self.inv_augmented_mass
1204                .solve_mut(&mut jacobians.rows_mut(j_id + ndofs, ndofs));
1205
1206            // lhs = Jᵀ M⁻¹ J = Σ J[k]·WJ[k]; only g1/g2 entries of J are nonzero.
1207            let lhs = jacobians[j_id + ndofs + g2] - c.coeff * jacobians[j_id + ndofs + g1];
1208
1209            let drift = q2 - c.coeff * q1 - c.offset;
1210
1211            out[i] = GenericJointConstraint {
1212                is_rigid_body1: false,
1213                solver_vel1: u32::MAX,
1214                ndofs1: 0,
1215                j_id1: 0,
1216                is_rigid_body2: false,
1217                solver_vel2: self.solver_id,
1218                ndofs2: ndofs,
1219                j_id2: j_id,
1220                joint_id: usize::MAX, // internal: no impulse writeback.
1221                impulse: 0.0,
1222                impulse_bounds: [-Real::MAX, Real::MAX],
1223                inv_lhs: crate::utils::inv(lhs),
1224                rhs: drift * erp_inv_dt,
1225                rhs_wo_bias: 0.0,
1226                cfm_coeff: 0.0,
1227                cfm_gain: 0.0,
1228                writeback_id: WritebackId::Dof(0),
1229            };
1230
1231            j_id += 2 * ndofs;
1232            i += 1;
1233        }
1234
1235        i
1236    }
1237
1238    /// The generalized velocity at the multibody_joint of the given link.
1239    #[inline]
1240    pub fn joint_velocity(&self, link: &MultibodyLink) -> DVectorView<'_, Real> {
1241        let ndofs = link.joint().ndofs();
1242        DVectorView::from_slice(
1243            &self.velocities.as_slice()[link.assembly_id..link.assembly_id + ndofs],
1244            ndofs,
1245        )
1246    }
1247
1248    /// The generalized accelerations of this multibodies.
1249    #[inline]
1250    pub fn generalized_acceleration(&self) -> DVectorView<'_, Real> {
1251        self.accelerations.rows(0, self.ndofs)
1252    }
1253
1254    /// The generalized velocities of this multibodies.
1255    #[inline]
1256    pub fn generalized_velocity(&self) -> DVectorView<'_, Real> {
1257        self.velocities.rows(0, self.ndofs)
1258    }
1259
1260    /// The body jacobian for link `link_id` calculated by the last call to [`Multibody::forward_kinematics`].
1261    #[inline]
1262    pub fn body_jacobian(&self, link_id: usize) -> &Jacobian<Real> {
1263        &self.body_jacobians[link_id]
1264    }
1265
1266    /// The mutable generalized velocities of this multibodies.
1267    #[inline]
1268    pub fn generalized_velocity_mut(&mut self) -> DVectorViewMut<'_, Real> {
1269        self.velocities.rows_mut(0, self.ndofs)
1270    }
1271
1272    #[inline]
1273    pub(crate) fn integrate(&mut self, dt: Real) {
1274        for rb in self.links.iter_mut() {
1275            rb.joint
1276                .integrate(dt, &self.velocities.as_slice()[rb.assembly_id..])
1277        }
1278    }
1279
1280    /// Apply displacements, in generalized coordinates, to this multibody.
1281    ///
1282    /// Note this does **not** updates the link poses, only their generalized coordinates.
1283    /// To update the link poses and associated rigid-bodies, call [`Self::forward_kinematics`].
1284    pub fn apply_displacements(&mut self, disp: &[Real]) {
1285        for link in self.links.iter_mut() {
1286            link.joint.apply_displacement(&disp[link.assembly_id..])
1287        }
1288    }
1289
1290    pub(crate) fn update_root_type(&mut self, bodies: &RigidBodySet, take_body_pose: bool) {
1291        if let Some(rb) = bodies.get(self.links[0].rigid_body) {
1292            if rb.is_dynamic() != self.root_is_dynamic {
1293                let root_pose = if take_body_pose {
1294                    *rb.position()
1295                } else {
1296                    self.links[0].local_to_world
1297                };
1298
1299                if rb.is_dynamic() {
1300                    let free_joint = MultibodyJoint::free(root_pose);
1301                    let prev_root_ndofs = self.links[0].joint().ndofs();
1302                    self.links[0].joint = free_joint;
1303                    self.links[0].assembly_id = 0;
1304                    self.ndofs += SPATIAL_DIM;
1305
1306                    self.velocities = self.velocities.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1307                    self.damping = self.damping.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1308                    self.armature = self.armature.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1309                    self.frictions = self.frictions.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1310                    self.accelerations =
1311                        self.accelerations.clone().insert_rows(0, SPATIAL_DIM, 0.0);
1312
1313                    for link in &mut self.links[1..] {
1314                        link.assembly_id += SPATIAL_DIM - prev_root_ndofs;
1315                    }
1316                } else {
1317                    assert!(self.velocities.len() >= SPATIAL_DIM);
1318                    assert!(self.damping.len() >= SPATIAL_DIM);
1319                    assert!(self.armature.len() >= SPATIAL_DIM);
1320                    assert!(self.frictions.len() >= SPATIAL_DIM);
1321                    assert!(self.accelerations.len() >= SPATIAL_DIM);
1322
1323                    let fixed_joint = MultibodyJoint::fixed(root_pose);
1324                    let prev_root_ndofs = self.links[0].joint().ndofs();
1325                    self.links[0].joint = fixed_joint;
1326                    self.links[0].assembly_id = 0;
1327                    self.ndofs -= prev_root_ndofs;
1328
1329                    if self.ndofs == 0 {
1330                        self.velocities = DVector::zeros(0);
1331                        self.damping = DVector::zeros(0);
1332                        self.armature = DVector::zeros(0);
1333                        self.frictions = DVector::zeros(0);
1334                        self.accelerations = DVector::zeros(0);
1335                    } else {
1336                        self.velocities =
1337                            self.velocities.index((prev_root_ndofs.., 0)).into_owned();
1338                        self.damping = self.damping.index((prev_root_ndofs.., 0)).into_owned();
1339                        self.armature = self.armature.index((prev_root_ndofs.., 0)).into_owned();
1340                        self.frictions = self.frictions.index((prev_root_ndofs.., 0)).into_owned();
1341                        self.accelerations = self
1342                            .accelerations
1343                            .index((prev_root_ndofs.., 0))
1344                            .into_owned();
1345                    }
1346
1347                    for link in &mut self.links[1..] {
1348                        link.assembly_id -= prev_root_ndofs;
1349                    }
1350                }
1351
1352                self.root_is_dynamic = rb.is_dynamic();
1353            }
1354
1355            // Make sure the positions are properly set to match the rigid-body’s.
1356            if take_body_pose {
1357                if self.links[0].joint.data.locked_axes.is_empty() {
1358                    self.links[0].joint.set_free_pos(*rb.position());
1359                } else {
1360                    self.links[0].joint.data.local_frame1 = *rb.position();
1361                }
1362            }
1363        }
1364    }
1365
1366    /// Update the rigid-body poses based on this multibody joint poses.
1367    ///
1368    /// This is typically called after [`Self::forward_kinematics`] to apply the new joint poses
1369    /// to the rigid-bodies.
1370    pub fn update_rigid_bodies(&self, bodies: &mut RigidBodySet, update_mass_properties: bool) {
1371        self.update_rigid_bodies_internal(bodies, update_mass_properties, false, true)
1372    }
1373
1374    pub(crate) fn update_rigid_bodies_internal(
1375        &self,
1376        bodies: &mut RigidBodySet,
1377        update_mass_properties: bool,
1378        update_next_positions_only: bool,
1379        change_tracking: bool,
1380    ) {
1381        // Handle the children. They all have a parent within this multibody.
1382        for link in self.links.iter() {
1383            let rb = if change_tracking {
1384                bodies.get_mut_internal_with_modification_tracking(link.rigid_body)
1385            } else {
1386                bodies.get_mut_internal(link.rigid_body)
1387            };
1388
1389            if let Some(rb) = rb {
1390                rb.pos.next_position = link.local_to_world;
1391
1392                if !update_next_positions_only {
1393                    rb.pos.position = link.local_to_world;
1394                }
1395
1396                if update_mass_properties {
1397                    rb.mprops
1398                        .update_world_mass_properties(rb.body_type, &link.local_to_world);
1399                }
1400            }
1401        }
1402    }
1403
1404    // TODO: make a version that doesn’t write back to bodies and doesn’t update the jacobians
1405    //       (i.e. just something used by the velocity solver’s small steps).
1406    /// Apply forward-kinematics to this multibody.
1407    ///
1408    /// This will update the [`MultibodyLink`] pose information as wall as the body jacobians.
1409    /// This will also ensure that the multibody has the proper number of degrees of freedom if
1410    /// its root node changed between dynamic and non-dynamic.
1411    ///
1412    /// Note that this does **not** update the poses of the [`RigidBody`] attached to the joints.
1413    /// Run [`Self::update_rigid_bodies`] to trigger that update.
1414    ///
1415    /// This method updates `self` with the result of the forward-kinematics operation.
1416    /// For a non-mutable version running forward kinematics on a single link, see
1417    /// [`Self::forward_kinematics_single_link`].
1418    ///
1419    /// ## Parameters
1420    /// - `bodies`: the set of rigid-bodies.
1421    /// - `read_root_pose_from_rigid_body`: if set to `true`, the root joint (either a fixed joint,
1422    ///   or a free joint) will have its pose set to its associated-rigid-body pose. Set this to `true`
1423    ///   when the root rigid-body pose has been modified and needs to affect the multibody.
1424    pub fn forward_kinematics(
1425        &mut self,
1426        bodies: &RigidBodySet,
1427        read_root_pose_from_rigid_body: bool,
1428    ) {
1429        // Be sure the degrees of freedom match and take the root position if needed.
1430        self.update_root_type(bodies, read_root_pose_from_rigid_body);
1431
1432        // Special case for the root, which has no parent.
1433        {
1434            let link = &mut self.links[0];
1435            link.local_to_parent = link.joint.body_to_parent();
1436            link.local_to_world = link.local_to_parent;
1437        }
1438
1439        // Handle the children. They all have a parent within this multibody.
1440        for i in 1..self.links.len() {
1441            let (link, parent_link) = self.links.get_mut_with_parent(i);
1442
1443            link.local_to_parent = link.joint.body_to_parent();
1444            link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1445
1446            {
1447                let parent_rb = &bodies[parent_link.rigid_body];
1448                let link_rb = &bodies[link.rigid_body];
1449                let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1450                let c2 = link.local_to_world * link.joint.data.local_frame2.translation;
1451                let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1452
1453                link.shift02 = c2 - c0;
1454                link.shift23 = c3 - c2;
1455            }
1456
1457            assert_eq!(
1458                bodies[link.rigid_body].body_type,
1459                RigidBodyType::Dynamic,
1460                "A rigid-body that is not at the root of a multibody must be dynamic."
1461            );
1462        }
1463
1464        /*
1465         * Compute body jacobians.
1466         */
1467        self.update_body_jacobians();
1468    }
1469
1470    /// Computes the ids of all the links between the root and the link identified by `link_id`.
1471    pub fn kinematic_branch(&self, link_id: usize) -> Vec<usize> {
1472        let mut branch = vec![]; // Perf: avoid allocation.
1473        let mut curr_id = Some(link_id);
1474
1475        while let Some(id) = curr_id {
1476            branch.push(id);
1477            curr_id = self.links[id].parent_id();
1478        }
1479
1480        branch.reverse();
1481        branch
1482    }
1483
1484    /// Apply forward-kinematics to compute the position of a single link of this multibody.
1485    ///
1486    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this link.
1487    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1488    /// is the sum of the current position of `self` and this `displacement`.
1489    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1490    //       that we are only traversing a single kinematic chain. Could this be refactored?
1491    pub fn forward_kinematics_single_link(
1492        &self,
1493        bodies: &RigidBodySet,
1494        link_id: usize,
1495        displacement: Option<&[Real]>,
1496        out_jacobian: Option<&mut Jacobian<Real>>,
1497    ) -> Pose {
1498        let branch = self.kinematic_branch(link_id);
1499        self.forward_kinematics_single_branch(bodies, &branch, displacement, out_jacobian)
1500    }
1501
1502    /// Apply forward-kinematics to compute the position of a single sorted branch of this multibody.
1503    ///
1504    /// The given `branch` must have the following properties:
1505    /// - It must be sorted, i.e., `branch[i] < branch[i + 1]`.
1506    /// - All the indices must be part of the same kinematic branch.
1507    /// - If a link is `branch[i]`, then `branch[i - 1]` must be its parent.
1508    ///
1509    /// In general, this method shouldn’t be used directly and [`Self::forward_kinematics_single_link`]
1510    /// should be preferred since it computes the branch indices automatically.
1511    ///
1512    /// If you want to calculate the branch indices manually, see [`Self::kinematic_branch`].
1513    ///
1514    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this branch.
1515    /// This represents the body jacobian for the last link in the branch.
1516    ///
1517    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1518    /// is the sum of the current position of `self` and this `displacement`.
1519    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1520    //       that we are only traversing a single kinematic chain. Could this be refactored?
1521    #[profiling::function]
1522    pub fn forward_kinematics_single_branch(
1523        &self,
1524        bodies: &RigidBodySet,
1525        branch: &[usize],
1526        displacement: Option<&[Real]>,
1527        mut out_jacobian: Option<&mut Jacobian<Real>>,
1528    ) -> Pose {
1529        if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1530            if out_jacobian.ncols() != self.ndofs {
1531                *out_jacobian = Jacobian::zeros(self.ndofs);
1532            } else {
1533                out_jacobian.fill(0.0);
1534            }
1535        }
1536
1537        let mut parent_link: Option<MultibodyLink> = None;
1538
1539        for i in branch {
1540            let mut link = self.links[*i];
1541
1542            if let Some(displacement) = displacement {
1543                link.joint
1544                    .apply_displacement(&displacement[link.assembly_id..]);
1545            }
1546
1547            let parent_to_world;
1548
1549            if let Some(parent_link) = parent_link {
1550                link.local_to_parent = link.joint.body_to_parent();
1551                link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1552
1553                {
1554                    let parent_rb = &bodies[parent_link.rigid_body];
1555                    let link_rb = &bodies[link.rigid_body];
1556                    let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1557                    let c2 = link.local_to_world
1558                        * Vector::from(link.joint.data.local_frame2.translation);
1559                    let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1560
1561                    link.shift02 = c2 - c0;
1562                    link.shift23 = c3 - c2;
1563                }
1564
1565                parent_to_world = parent_link.local_to_world;
1566
1567                if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1568                    let (mut link_j_v, parent_j_w) =
1569                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1570                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
1571                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
1572                }
1573            } else {
1574                link.local_to_parent = link.joint.body_to_parent();
1575                link.local_to_world = link.local_to_parent;
1576                parent_to_world = Pose::IDENTITY;
1577            }
1578
1579            if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1580                let ndofs = link.joint.ndofs();
1581                let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
1582                let mut link_joint_j = tmp.columns_mut(0, ndofs);
1583                let mut link_j_part = out_jacobian.columns_mut(link.assembly_id, ndofs);
1584                link.joint.jacobian(
1585                    &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
1586                    &mut link_joint_j,
1587                );
1588                link_j_part += link_joint_j;
1589
1590                {
1591                    let (mut link_j_v, link_j_w) =
1592                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1593                    let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
1594                    link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
1595                }
1596            }
1597
1598            parent_link = Some(link);
1599        }
1600
1601        parent_link
1602            .map(|link| link.local_to_world)
1603            .unwrap_or(Pose::IDENTITY)
1604    }
1605
1606    /// The total number of freedoms of this multibody.
1607    #[inline]
1608    pub fn ndofs(&self) -> usize {
1609        self.ndofs
1610    }
1611
1612    pub(crate) fn fill_jacobians(
1613        &self,
1614        link_id: usize,
1615        unit_force: Vector,
1616        unit_torque: AngVector,
1617        j_id: &mut usize,
1618        jacobians: &mut DVector,
1619    ) -> (Real, Real) {
1620        if self.ndofs == 0 {
1621            return (0.0, 0.0);
1622        }
1623
1624        let wj_id = *j_id + self.ndofs;
1625        let force = Force {
1626            linear: unit_force,
1627            #[cfg(feature = "dim2")]
1628            angular: unit_torque,
1629            #[cfg(feature = "dim3")]
1630            angular: unit_torque,
1631        };
1632
1633        let link = &self.links[link_id];
1634        let mut out_j = jacobians.rows_mut(*j_id, self.ndofs);
1635        self.body_jacobians[link.internal_id].tr_mul_to(force.as_vector(), &mut out_j);
1636
1637        // TODO: Optimize with a copy_nonoverlapping?
1638        for i in 0..self.ndofs {
1639            jacobians[wj_id + i] = jacobians[*j_id + i];
1640        }
1641
1642        {
1643            let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs);
1644            self.augmented_mass_indices
1645                .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| {
1646                    self.inv_augmented_mass.solve_mut(out_invm_j);
1647                });
1648        }
1649
1650        let j = jacobians.rows(*j_id, self.ndofs);
1651        let invm_j = jacobians.rows(wj_id, self.ndofs);
1652        *j_id += self.ndofs * 2;
1653
1654        (j.dot(&invm_j), j.dot(&self.generalized_velocity()))
1655    }
1656
1657    /// Fills `jacobians` with the relative jacobian `J = J2ᵀ·f2 − J1ᵀ·f1` of two links of `self`, then `M⁻¹·J`
1658    /// (e.g. a loop closure). The difference must be explicit: per-link blocks lose the `J1ᵀ·W·J2` effective-mass
1659    /// coupling since both act on the same generalized velocities. Cancellation-vanished rows are zeroed so the solver skips them.
1660    pub(crate) fn fill_relative_jacobians(
1661        &self,
1662        link_id1: usize,
1663        unit_force1: Vector,
1664        unit_torque1: AngVector,
1665        link_id2: usize,
1666        unit_force2: Vector,
1667        unit_torque2: AngVector,
1668        j_id: &mut usize,
1669        jacobians: &mut DVector,
1670    ) {
1671        if self.ndofs == 0 {
1672            return;
1673        }
1674
1675        let wj_id = *j_id + self.ndofs;
1676        let force1 = Force {
1677            linear: unit_force1,
1678            angular: unit_torque1,
1679        };
1680        let force2 = Force {
1681            linear: unit_force2,
1682            angular: unit_torque2,
1683        };
1684
1685        let link1 = &self.links[link_id1];
1686        let link2 = &self.links[link_id2];
1687
1688        {
1689            let jb1 = &self.body_jacobians[link1.internal_id];
1690            let jb2 = &self.body_jacobians[link2.internal_id];
1691
1692            // Use the (overwritten below) W·J slot as workspace for J1ᵀ·f1.
1693            let (mut out_j, mut workspace) =
1694                jacobians.rows_range_pair_mut(*j_id..*j_id + self.ndofs, wj_id..wj_id + self.ndofs);
1695            jb2.tr_mul_to(force2.as_vector(), &mut out_j);
1696            jb1.tr_mul_to(force1.as_vector(), &mut workspace);
1697            out_j.axpy(-1.0, &workspace, 1.0);
1698
1699            // Cancellation guard: the reference scale is the magnitude of the dot-product operands,
1700            // not their results (which may be pure cancellation noise when the direction isn’t
1701            // expressible by the dofs). Rows below ~1000·ε times that scale are noise, not a constraint.
1702            let scale_sq = jb1.norm_squared() * force1.as_vector().norm_squared()
1703                + jb2.norm_squared() * force2.as_vector().norm_squared();
1704            let eps = Real::EPSILON * 1.0e3;
1705            if out_j.norm_squared() <= eps * eps * scale_sq {
1706                out_j.fill(0.0);
1707            }
1708        }
1709
1710        // TODO: Optimize with a copy_nonoverlapping?
1711        for i in 0..self.ndofs {
1712            jacobians[wj_id + i] = jacobians[*j_id + i];
1713        }
1714
1715        {
1716            let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs);
1717            self.augmented_mass_indices
1718                .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| {
1719                    self.inv_augmented_mass.solve_mut(out_invm_j);
1720                });
1721        }
1722
1723        *j_id += self.ndofs * 2;
1724    }
1725}
1726
1727#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1728#[derive(Clone, Debug)]
1729struct IndexSequence {
1730    first_to_remove: u32,
1731    index_map: Vec<usize>,
1732}
1733
1734impl IndexSequence {
1735    const NONE: u32 = u32::MAX;
1736
1737    fn new() -> Self {
1738        Self {
1739            first_to_remove: Self::NONE,
1740            index_map: vec![],
1741        }
1742    }
1743
1744    /// Index of the first removed dof, assuming there is one.
1745    fn start(&self) -> usize {
1746        self.first_to_remove as usize
1747    }
1748
1749    fn clear(&mut self) {
1750        self.first_to_remove = Self::NONE;
1751        self.index_map.clear();
1752    }
1753
1754    fn keep(&mut self, i: usize) {
1755        if self.first_to_remove == Self::NONE {
1756            // Nothing got removed yet. No need to register any
1757            // special indexing.
1758            return;
1759        }
1760
1761        self.index_map.push(i);
1762    }
1763
1764    fn remove(&mut self, i: usize) {
1765        if self.first_to_remove == Self::NONE {
1766            self.first_to_remove = i as u32;
1767        }
1768    }
1769
1770    fn dim_after_removal(&self, original_dim: usize) -> usize {
1771        if self.first_to_remove == Self::NONE {
1772            original_dim
1773        } else {
1774            self.start() + self.index_map.len()
1775        }
1776    }
1777
1778    fn rearrange_columns<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1779        &self,
1780        mat: &mut na::Matrix<Real, R, C, S>,
1781        clear_removed: bool,
1782    ) {
1783        if self.first_to_remove == Self::NONE {
1784            // Nothing to rearrange.
1785            return;
1786        }
1787
1788        for (target_shift, source) in self.index_map.iter().enumerate() {
1789            let target = self.start() + target_shift;
1790            let (mut target_col, source_col) = mat.columns_range_pair_mut(target, *source);
1791            target_col.copy_from(&source_col);
1792        }
1793
1794        if clear_removed {
1795            mat.columns_range_mut(self.start() + self.index_map.len()..)
1796                .fill(0.0);
1797        }
1798    }
1799
1800    fn rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1801        &self,
1802        mat: &mut na::Matrix<Real, R, C, S>,
1803        clear_removed: bool,
1804    ) {
1805        if self.first_to_remove == Self::NONE {
1806            // Nothing to rearrange.
1807            return;
1808        }
1809
1810        for mut col in mat.column_iter_mut() {
1811            for (target_shift, source) in self.index_map.iter().enumerate() {
1812                let target = self.start() + target_shift;
1813                col[target] = col[*source];
1814            }
1815
1816            if clear_removed {
1817                col.rows_range_mut(self.start() + self.index_map.len()..)
1818                    .fill(0.0);
1819            }
1820        }
1821    }
1822
1823    fn inv_rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1824        &self,
1825        mat: &mut na::Matrix<Real, R, C, S>,
1826    ) {
1827        if self.first_to_remove == Self::NONE {
1828            // Nothing to rearrange.
1829            return;
1830        }
1831
1832        for mut col in mat.column_iter_mut() {
1833            for (target_shift, source) in self.index_map.iter().enumerate().rev() {
1834                let target = self.start() + target_shift;
1835                col[*source] = col[target];
1836                col[target] = 0.0;
1837            }
1838        }
1839    }
1840
1841    fn with_rearranged_rows_mut<C: na::Dim, S: StorageMut<Real, Dyn, C>>(
1842        &self,
1843        mat: &mut na::Matrix<Real, Dyn, C, S>,
1844        mut f: impl FnMut(&mut na::MatrixViewMut<Real, Dyn, C, S::RStride, S::CStride>),
1845    ) {
1846        self.rearrange_rows(mat, true);
1847        let effective_dim = self.dim_after_removal(mat.nrows());
1848        if effective_dim > 0 {
1849            f(&mut mat.rows_mut(0, effective_dim));
1850        }
1851        self.inv_rearrange_rows(mat);
1852    }
1853}
1854
1855#[cfg(test)]
1856mod test {
1857    use super::IndexSequence;
1858    use crate::alloc_prelude::*;
1859    use crate::dynamics::{ImpulseJointSet, IslandManager};
1860    #[cfg(feature = "dim3")]
1861    use crate::math::Vector;
1862    use crate::math::{Real, SPATIAL_DIM};
1863    use crate::prelude::{
1864        ColliderSet, MultibodyJointHandle, MultibodyJointSet, RevoluteJoint, RigidBodyBuilder,
1865        RigidBodySet, SoftBodySet,
1866    };
1867    use na::{DVector, RowDVector};
1868
1869    #[test]
1870    fn test_multibody_append() {
1871        let mut bodies = RigidBodySet::new();
1872        let mut joints = MultibodyJointSet::new();
1873
1874        let a = bodies.insert(RigidBodyBuilder::dynamic());
1875        let b = bodies.insert(RigidBodyBuilder::dynamic());
1876        let c = bodies.insert(RigidBodyBuilder::dynamic());
1877        let d = bodies.insert(RigidBodyBuilder::dynamic());
1878
1879        #[cfg(feature = "dim2")]
1880        let joint = RevoluteJoint::new();
1881        #[cfg(feature = "dim3")]
1882        let joint = RevoluteJoint::new(Vector::X);
1883
1884        let mb_handle = joints.insert(a, b, joint, true).unwrap();
1885        joints.insert(c, d, joint, true).unwrap();
1886        joints.insert(b, c, joint, true).unwrap();
1887
1888        assert_eq!(joints.get(mb_handle).unwrap().0.ndofs, SPATIAL_DIM + 3);
1889    }
1890
1891    #[test]
1892    fn test_multibody_insert() {
1893        let mut rnd = oorandom::Rand32::new(1234);
1894
1895        for k in 0..10 {
1896            let mut bodies = RigidBodySet::new();
1897            let mut multibody_joints = MultibodyJointSet::new();
1898
1899            let num_links = 100;
1900            let mut handles = vec![];
1901
1902            for _ in 0..num_links {
1903                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1904            }
1905
1906            let mut insertion_id: Vec<_> = (0..num_links - 1).collect();
1907
1908            #[cfg(feature = "dim2")]
1909            let joint = RevoluteJoint::new();
1910            #[cfg(feature = "dim3")]
1911            let joint = RevoluteJoint::new(Vector::X);
1912
1913            match k {
1914                0 => {} // Remove in insertion order.
1915                1 => {
1916                    // Remove from leaf to root.
1917                    insertion_id.reverse();
1918                }
1919                _ => {
1920                    // Shuffle the vector a bit.
1921                    // (This test checks multiple shuffle arrangements due to k > 2).
1922                    for l in 0..num_links - 1 {
1923                        insertion_id.swap(l, rnd.rand_range(0..num_links as u32 - 1) as usize);
1924                    }
1925                }
1926            }
1927
1928            let mut mb_handle = MultibodyJointHandle::invalid();
1929            for i in insertion_id {
1930                mb_handle = multibody_joints
1931                    .insert(handles[i], handles[i + 1], joint, true)
1932                    .unwrap();
1933            }
1934
1935            assert_eq!(
1936                multibody_joints.get(mb_handle).unwrap().0.ndofs,
1937                SPATIAL_DIM + num_links - 1
1938            );
1939        }
1940    }
1941
1942    #[test]
1943    fn test_multibody_remove() {
1944        let mut rnd = oorandom::Rand32::new(1234);
1945
1946        for k in 0..10 {
1947            let mut bodies = RigidBodySet::new();
1948            let mut multibody_joints = MultibodyJointSet::new();
1949            let mut colliders = ColliderSet::new();
1950            let mut impulse_joints = ImpulseJointSet::new();
1951            let mut islands = IslandManager::new();
1952            let mut soft_bodies = SoftBodySet::new();
1953
1954            let num_links = 100;
1955            let mut handles = vec![];
1956
1957            for _ in 0..num_links {
1958                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1959            }
1960
1961            #[cfg(feature = "dim2")]
1962            let joint = RevoluteJoint::new();
1963            #[cfg(feature = "dim3")]
1964            let joint = RevoluteJoint::new(Vector::X);
1965
1966            for i in 0..num_links - 1 {
1967                multibody_joints
1968                    .insert(handles[i], handles[i + 1], joint, true)
1969                    .unwrap();
1970            }
1971
1972            match k {
1973                0 => {} // Remove in insertion order.
1974                1 => {
1975                    // Remove from leaf to root.
1976                    handles.reverse();
1977                }
1978                _ => {
1979                    // Shuffle the vector a bit.
1980                    // (This test checks multiple shuffle arrangements due to k > 2).
1981                    for l in 0..num_links {
1982                        handles.swap(l, rnd.rand_range(0..num_links as u32) as usize);
1983                    }
1984                }
1985            }
1986
1987            for handle in handles {
1988                bodies.remove(
1989                    handle,
1990                    &mut islands,
1991                    &mut colliders,
1992                    &mut impulse_joints,
1993                    &mut multibody_joints,
1994                    &mut soft_bodies,
1995                    true,
1996                );
1997            }
1998        }
1999    }
2000
2001    fn test_sequence() -> IndexSequence {
2002        let mut seq = IndexSequence::new();
2003        seq.remove(2);
2004        seq.remove(3);
2005        seq.remove(4);
2006        seq.keep(5);
2007        seq.keep(6);
2008        seq.remove(7);
2009        seq.keep(8);
2010        seq
2011    }
2012
2013    #[test]
2014    fn index_sequence_rearrange_columns() {
2015        let seq = test_sequence();
2016        let mut vec = RowDVector::from_fn(10, |_, c| c as Real);
2017        seq.rearrange_columns(&mut vec, true);
2018        assert_eq!(
2019            vec,
2020            RowDVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
2021        );
2022    }
2023
2024    #[test]
2025    fn index_sequence_rearrange_rows() {
2026        let seq = test_sequence();
2027        let mut vec = DVector::from_fn(10, |r, _| r as Real);
2028        seq.rearrange_rows(&mut vec, true);
2029        assert_eq!(
2030            vec,
2031            DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
2032        );
2033        seq.inv_rearrange_rows(&mut vec);
2034        assert_eq!(
2035            vec,
2036            DVector::from(vec![0.0, 1.0, 0.0, 0.0, 0.0, 5.0, 6.0, 0.0, 8.0, 0.0])
2037        );
2038    }
2039
2040    #[test]
2041    fn index_sequence_with_rearranged_rows_mut() {
2042        let seq = test_sequence();
2043        let mut vec = DVector::from_fn(10, |r, _| r as Real);
2044        seq.with_rearranged_rows_mut(&mut vec, |v| {
2045            assert_eq!(v.len(), 5);
2046            assert_eq!(*v, DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0]));
2047            *v *= 10.0;
2048        });
2049        assert_eq!(
2050            vec,
2051            DVector::from(vec![0.0, 10.0, 0.0, 0.0, 0.0, 50.0, 60.0, 0.0, 80.0, 0.0])
2052        );
2053    }
2054}