Skip to main content

rapier3d/dynamics/joint/multibody_joint/
multibody.rs

1use super::multibody_link::{MultibodyLink, MultibodyLinkVec};
2use super::multibody_workspace::MultibodyWorkspace;
3use crate::dynamics::{RigidBodyHandle, RigidBodySet, RigidBodyType, RigidBodyVelocity};
4use crate::math::{
5    ANG_DIM, AngDim, AngVector, DIM, DVector, Dim, Jacobian, Pose, Real, SPATIAL_DIM,
6    SimdAngVector, Vector,
7};
8use crate::prelude::MultibodyJoint;
9#[cfg(feature = "dim3")]
10use crate::utils::mat_to_na;
11use crate::utils::{AngularInertiaOps, CrossProduct, CrossProductMatrix, IndexMut2, vect_to_na};
12use na::{
13    self, DMatrix, DVectorView, DVectorViewMut, Dyn, LU, OMatrix, SMatrix, SVector, StorageMut,
14};
15
16#[cfg(doc)]
17use crate::prelude::{GenericJoint, RigidBody};
18
19#[repr(C)]
20#[derive(Copy, Clone, Debug, Default)]
21struct Force {
22    linear: Vector,
23    angular: AngVector,
24}
25
26impl Force {
27    fn new(linear: Vector, angular: AngVector) -> Self {
28        Self { linear, angular }
29    }
30
31    fn as_vector(&self) -> &SVector<Real, SPATIAL_DIM> {
32        unsafe { std::mem::transmute(self) }
33    }
34}
35
36#[cfg(feature = "dim2")]
37fn concat_rb_mass_matrix(mass: Vector, inertia: Real) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
38    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
39    result[(0, 0)] = mass.x;
40    result[(1, 1)] = mass.y;
41    result[(2, 2)] = inertia;
42    result
43}
44
45#[cfg(feature = "dim3")]
46fn concat_rb_mass_matrix(
47    mass: Vector,
48    inertia: na::Matrix3<Real>,
49) -> SMatrix<Real, SPATIAL_DIM, SPATIAL_DIM> {
50    let mut result = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
51    result[(0, 0)] = mass.x;
52    result[(1, 1)] = mass.y;
53    result[(2, 2)] = mass.z;
54    result
55        .fixed_view_mut::<ANG_DIM, ANG_DIM>(DIM, DIM)
56        .copy_from(&inertia);
57    result
58}
59
60/// An articulated body simulated using the reduced-coordinates approach.
61#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
62#[derive(Clone, Debug)]
63pub struct Multibody {
64    // TODO: serialization: skip the workspace fields.
65    pub(crate) links: MultibodyLinkVec,
66    pub(crate) velocities: DVector,
67    pub(crate) damping: DVector,
68    pub(crate) accelerations: DVector,
69
70    body_jacobians: Vec<Jacobian<Real>>,
71    // NOTE: the mass matrices are dimensioned based on the non-kinematic degrees of
72    //       freedoms only. The `Self::augmented_mass_permutation` sequence can be used to
73    //       move dofs from/to a format that matches the augmented mass.
74    // TODO: use sparse matrices?
75    augmented_mass: DMatrix<Real>,
76    inv_augmented_mass: LU<Real, Dyn, Dyn>,
77    // The indexing sequence for moving all kinematics degrees of
78    // freedoms to the end of the generalized coordinates vector.
79    augmented_mass_indices: IndexSequence,
80
81    acc_augmented_mass: DMatrix<Real>,
82    acc_inv_augmented_mass: LU<Real, Dyn, Dyn>,
83
84    ndofs: usize,
85    pub(crate) root_is_dynamic: bool,
86    pub(crate) solver_id: u32,
87    self_contacts_enabled: bool,
88
89    /*
90     * Workspaces.
91     */
92    workspace: MultibodyWorkspace,
93    coriolis_v: Vec<OMatrix<Real, Dim, Dyn>>,
94    coriolis_w: Vec<OMatrix<Real, AngDim, Dyn>>,
95    i_coriolis_dt: Jacobian<Real>,
96}
97impl Default for Multibody {
98    fn default() -> Self {
99        Multibody::new()
100    }
101}
102
103impl Multibody {
104    /// Creates a new multibody with no link.
105    pub fn new() -> Self {
106        Self::with_self_contacts(true)
107    }
108
109    pub(crate) fn with_self_contacts(self_contacts_enabled: bool) -> Self {
110        Multibody {
111            links: MultibodyLinkVec(Vec::new()),
112            velocities: DVector::zeros(0),
113            damping: DVector::zeros(0),
114            accelerations: DVector::zeros(0),
115            body_jacobians: Vec::new(),
116            augmented_mass: DMatrix::zeros(0, 0),
117            inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
118            acc_augmented_mass: DMatrix::zeros(0, 0),
119            acc_inv_augmented_mass: LU::new(DMatrix::zeros(0, 0)),
120            augmented_mass_indices: IndexSequence::new(),
121            ndofs: 0,
122            solver_id: 0,
123            workspace: MultibodyWorkspace::new(),
124            coriolis_v: Vec::new(),
125            coriolis_w: Vec::new(),
126            i_coriolis_dt: Jacobian::zeros(0),
127            root_is_dynamic: false,
128            self_contacts_enabled,
129            // solver_workspace: Some(SolverWorkspace::new()),
130        }
131    }
132
133    pub(crate) fn with_root(handle: RigidBodyHandle, self_contacts_enabled: bool) -> Self {
134        let mut mb = Multibody::with_self_contacts(self_contacts_enabled);
135        // NOTE: we have no way of knowing if the root in fixed at this point, so
136        //       we mark it as dynamic and will fix later with `Self::update_root_type`.
137        mb.root_is_dynamic = true;
138        let joint = MultibodyJoint::free(Pose::IDENTITY);
139        mb.add_link(None, joint, handle);
140        mb
141    }
142
143    pub(crate) fn remove_link(self, to_remove: usize, joint_only: bool) -> Vec<Multibody> {
144        let mut result = vec![];
145        let mut link2mb = vec![usize::MAX; self.links.len()];
146        let mut link_id2new_id = vec![usize::MAX; self.links.len()];
147
148        // Split multibody and update the set of links and ndofs.
149        for (i, mut link) in self.links.0.into_iter().enumerate() {
150            let is_new_root = i == 0
151                || !joint_only && link.parent_internal_id == to_remove
152                || joint_only && i == to_remove;
153
154            if !joint_only && i == to_remove {
155                continue;
156            } else if is_new_root {
157                link2mb[i] = result.len();
158                result.push(Multibody::with_self_contacts(self.self_contacts_enabled));
159            } else {
160                link2mb[i] = link2mb[link.parent_internal_id]
161            }
162
163            let curr_mb = &mut result[link2mb[i]];
164            link_id2new_id[i] = curr_mb.links.len();
165
166            if is_new_root {
167                let joint = MultibodyJoint::fixed(*link.local_to_world());
168                link.joint = joint;
169            }
170
171            curr_mb.ndofs += link.joint().ndofs();
172            curr_mb.links.push(link);
173        }
174
175        // Adjust all the internal ids, and copy the data from the
176        // previous multibody to the new one.
177        for mb in &mut result {
178            mb.grow_buffers(mb.ndofs, mb.links.len());
179            mb.workspace.resize(mb.links.len(), mb.ndofs);
180
181            let mut assembly_id = 0;
182            for (i, link) in mb.links.iter_mut().enumerate() {
183                let link_ndofs = link.joint().ndofs();
184                mb.velocities
185                    .rows_mut(assembly_id, link_ndofs)
186                    .copy_from(&self.velocities.rows(link.assembly_id, link_ndofs));
187                mb.damping
188                    .rows_mut(assembly_id, link_ndofs)
189                    .copy_from(&self.damping.rows(link.assembly_id, link_ndofs));
190                mb.accelerations
191                    .rows_mut(assembly_id, link_ndofs)
192                    .copy_from(&self.accelerations.rows(link.assembly_id, link_ndofs));
193
194                link.internal_id = i;
195                link.assembly_id = assembly_id;
196
197                // NOTE: for the root, the current`link.parent_internal_id` is invalid since that
198                //       parent lies in a different multibody now.
199                link.parent_internal_id = if i != 0 {
200                    link_id2new_id[link.parent_internal_id]
201                } else {
202                    0
203                };
204                assembly_id += link_ndofs;
205            }
206        }
207
208        result
209    }
210
211    pub(crate) fn append(&mut self, mut rhs: Multibody, parent: usize, joint: MultibodyJoint) {
212        let rhs_root_ndofs = rhs.links[0].joint.ndofs();
213        // Values for rhs will be copied into the buffers of `self` starting at this index.
214        let rhs_copy_shift = self.ndofs + joint.ndofs();
215        // Number of dofs to copy from rhs. The root’s dofs isn’t included because it will be
216        // replaced by `joint.
217        let rhs_copy_ndofs = rhs.ndofs - rhs_root_ndofs;
218
219        // Adjust the ids of all the rhs links except the first one.
220        let base_assembly_id = self.velocities.len() - rhs_root_ndofs + joint.ndofs();
221        let base_internal_id = self.links.len() + 1;
222        let base_parent_id = self.links.len();
223
224        for link in &mut rhs.links.0[1..] {
225            link.assembly_id += base_assembly_id;
226            link.internal_id += base_internal_id;
227            link.parent_internal_id += base_parent_id;
228        }
229
230        // Adjust the first link.
231        {
232            rhs.links[0].joint = joint;
233            rhs.links[0].assembly_id = self.velocities.len();
234            rhs.links[0].internal_id = self.links.len();
235            rhs.links[0].parent_internal_id = parent;
236        }
237
238        // Grow buffers then append data from rhs.
239        self.grow_buffers(rhs_copy_ndofs + rhs.links[0].joint.ndofs(), rhs.links.len());
240
241        if rhs_copy_ndofs > 0 {
242            self.velocities
243                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
244                .copy_from(&rhs.velocities.rows(rhs_root_ndofs, rhs_copy_ndofs));
245            self.damping
246                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
247                .copy_from(&rhs.damping.rows(rhs_root_ndofs, rhs_copy_ndofs));
248            self.accelerations
249                .rows_mut(rhs_copy_shift, rhs_copy_ndofs)
250                .copy_from(&rhs.accelerations.rows(rhs_root_ndofs, rhs_copy_ndofs));
251        }
252
253        rhs.links[0]
254            .joint
255            .default_damping(&mut self.damping.rows_mut(base_assembly_id, rhs_root_ndofs));
256
257        self.links.append(&mut rhs.links);
258        self.ndofs = self.velocities.len();
259        self.workspace.resize(self.links.len(), self.ndofs);
260    }
261
262    /// Whether self-contacts are enabled on this multibody.
263    ///
264    /// If set to `false` no two link from this multibody can generate contacts, even
265    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
266    pub fn self_contacts_enabled(&self) -> bool {
267        self.self_contacts_enabled
268    }
269
270    /// Sets whether self-contacts are enabled on this multibody.
271    ///
272    /// If set to `false` no two link from this multibody can generate contacts, even
273    /// if the contact is enabled on the individual joint with [`GenericJoint::contacts_enabled`].
274    pub fn set_self_contacts_enabled(&mut self, enabled: bool) {
275        self.self_contacts_enabled = enabled;
276    }
277
278    /// The inverse augmented mass matrix of this multibody.
279    pub fn inv_augmented_mass(&self) -> &LU<Real, Dyn, Dyn> {
280        &self.inv_augmented_mass
281    }
282
283    /// The first link of this multibody.
284    #[inline]
285    pub fn root(&self) -> &MultibodyLink {
286        &self.links[0]
287    }
288
289    /// Mutable reference to the first link of this multibody.
290    #[inline]
291    pub fn root_mut(&mut self) -> &mut MultibodyLink {
292        &mut self.links[0]
293    }
294
295    /// Reference `i`-th multibody link of this multibody.
296    ///
297    /// Return `None` if there is less than `i + 1` multibody links.
298    #[inline]
299    pub fn link(&self, id: usize) -> Option<&MultibodyLink> {
300        self.links.get(id)
301    }
302
303    /// Mutable reference to the multibody link with the given id.
304    ///
305    /// Return `None` if the given id does not identifies a multibody link part of `self`.
306    #[inline]
307    pub fn link_mut(&mut self, id: usize) -> Option<&mut MultibodyLink> {
308        self.links.get_mut(id)
309    }
310
311    /// The number of links on this multibody.
312    pub fn num_links(&self) -> usize {
313        self.links.len()
314    }
315
316    /// Iterator through all the links of this multibody.
317    ///
318    /// All link are guaranteed to be yielded before its descendant.
319    pub fn links(&self) -> impl Iterator<Item = &MultibodyLink> {
320        self.links.iter()
321    }
322
323    /// Mutable iterator through all the links of this multibody.
324    ///
325    /// All link are guaranteed to be yielded before its descendant.
326    pub fn links_mut(&mut self) -> impl Iterator<Item = &mut MultibodyLink> {
327        self.links.iter_mut()
328    }
329
330    /// The vector of damping applied to this multibody.
331    #[inline]
332    pub fn damping(&self) -> &DVector {
333        &self.damping
334    }
335
336    /// Mutable vector of damping applied to this multibody.
337    #[inline]
338    pub fn damping_mut(&mut self) -> &mut DVector {
339        &mut self.damping
340    }
341
342    pub(crate) fn add_link(
343        &mut self,
344        parent: Option<usize>, // TODO: should be a RigidBodyHandle?
345        dof: MultibodyJoint,
346        body: RigidBodyHandle,
347    ) -> &mut MultibodyLink {
348        assert!(
349            parent.is_none() || !self.links.is_empty(),
350            "Multibody::build_body: invalid parent id."
351        );
352
353        /*
354         * Compute the indices.
355         */
356        let assembly_id = self.velocities.len();
357        let internal_id = self.links.len();
358
359        /*
360         * Grow the buffers.
361         */
362        let ndofs = dof.ndofs();
363        self.grow_buffers(ndofs, 1);
364        self.ndofs += ndofs;
365
366        /*
367         * Setup default damping.
368         */
369        dof.default_damping(&mut self.damping.rows_mut(assembly_id, ndofs));
370
371        /*
372         * Create the multibody.
373         */
374        let local_to_parent = dof.body_to_parent();
375        let local_to_world;
376
377        let parent_internal_id;
378        if let Some(parent) = parent {
379            parent_internal_id = parent;
380            let parent_link = &mut self.links[parent_internal_id];
381            local_to_world = parent_link.local_to_world * local_to_parent;
382        } else {
383            parent_internal_id = 0;
384            local_to_world = local_to_parent;
385        }
386
387        let rb = MultibodyLink::new(
388            body,
389            internal_id,
390            assembly_id,
391            parent_internal_id,
392            dof,
393            local_to_world,
394            local_to_parent,
395        );
396
397        self.links.push(rb);
398        self.workspace.resize(self.links.len(), self.ndofs);
399
400        &mut self.links[internal_id]
401    }
402
403    fn grow_buffers(&mut self, ndofs: usize, num_jacobians: usize) {
404        let len = self.velocities.len();
405        self.velocities.resize_vertically_mut(len + ndofs, 0.0);
406        self.damping.resize_vertically_mut(len + ndofs, 0.0);
407        self.accelerations.resize_vertically_mut(len + ndofs, 0.0);
408        self.body_jacobians
409            .extend((0..num_jacobians).map(|_| Jacobian::zeros(0)));
410    }
411
412    pub(crate) fn update_acceleration(&mut self, bodies: &RigidBodySet) {
413        if self.ndofs == 0 {
414            return; // Nothing to do.
415        }
416
417        self.accelerations.fill(0.0);
418
419        // Eqn 42 to 45
420        for i in 0..self.links.len() {
421            let link = &self.links[i];
422            let rb = &bodies[link.rigid_body];
423
424            let mut acc = RigidBodyVelocity::zero();
425
426            if i != 0 {
427                let parent_id = link.parent_internal_id;
428                let parent_link = &self.links[parent_id];
429                let parent_rb = &bodies[parent_link.rigid_body];
430
431                acc += self.workspace.accs[parent_id];
432                // The 2.0 originates from the two identical terms of Jdot (the terms become
433                // identical once they are multiplied by the generalized velocities).
434                acc.linvel += 2.0 * parent_rb.vels.angvel.gcross(link.joint_velocity.linvel);
435                #[cfg(feature = "dim3")]
436                {
437                    acc.angvel += parent_rb.vels.angvel.cross(link.joint_velocity.angvel);
438                }
439
440                acc.linvel += parent_rb
441                    .vels
442                    .angvel
443                    .gcross(parent_rb.vels.angvel.gcross(link.shift02));
444                acc.linvel += self.workspace.accs[parent_id].angvel.gcross(link.shift02);
445            }
446
447            acc.linvel += rb.vels.angvel.gcross(rb.vels.angvel.gcross(link.shift23));
448            acc.linvel += acc.angvel.gcross(link.shift23);
449
450            self.workspace.accs[i] = acc;
451
452            // TODO: should gyroscopic forces already be computed by the rigid-body itself
453            //       (at the same time that we add the gravity force)?
454            let gyroscopic;
455            let rb_inertia = rb.mprops.effective_angular_inertia();
456            let rb_mass = rb.mprops.effective_mass();
457
458            #[cfg(feature = "dim3")]
459            {
460                let angvel = rb.vels.angvel;
461                let inertia_times_angvel = rb_inertia * angvel;
462                gyroscopic = angvel.cross(inertia_times_angvel);
463            }
464            #[cfg(feature = "dim2")]
465            {
466                gyroscopic = 0.0;
467            }
468
469            let external_forces = Force::new(
470                rb.forces.force - rb_mass * acc.linvel,
471                rb.forces.torque - gyroscopic - rb_inertia * acc.angvel,
472            );
473            self.accelerations.gemv_tr(
474                1.0,
475                &self.body_jacobians[i],
476                external_forces.as_vector(),
477                1.0,
478            );
479        }
480
481        self.accelerations
482            .cmpy(-1.0, &self.damping, &self.velocities, 1.0);
483
484        self.augmented_mass_indices
485            .with_rearranged_rows_mut(&mut self.accelerations, |accs| {
486                self.acc_inv_augmented_mass.solve_mut(accs);
487            });
488    }
489
490    /// Computes the constant terms of the dynamics.
491    #[profiling::function]
492    pub(crate) fn update_dynamics(&mut self, dt: Real, bodies: &mut RigidBodySet) {
493        /*
494         * Compute velocities.
495         * NOTE: this is needed for kinematic bodies too.
496         */
497        let link = &mut self.links[0];
498        let joint_velocity = link
499            .joint
500            .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
501
502        link.joint_velocity = joint_velocity;
503        bodies.index_mut_internal(link.rigid_body).vels = link.joint_velocity;
504
505        for i in 1..self.links.len() {
506            let (link, parent_link) = self.links.get_mut_with_parent(i);
507            let rb = &bodies[link.rigid_body];
508            let parent_rb = &bodies[parent_link.rigid_body];
509
510            let joint_velocity = link
511                .joint
512                .jacobian_mul_coordinates(&self.velocities.as_slice()[link.assembly_id..]);
513            link.joint_velocity = joint_velocity.transformed(
514                &(parent_link.local_to_world.rotation * link.joint.data.local_frame1.rotation),
515            );
516            let mut new_rb_vels = parent_rb.vels + link.joint_velocity;
517            let shift = rb.mprops.world_com - parent_rb.mprops.world_com;
518            new_rb_vels.linvel += parent_rb.vels.angvel.gcross(shift);
519            new_rb_vels.linvel += link.joint_velocity.angvel.gcross(link.shift23);
520
521            bodies.index_mut_internal(link.rigid_body).vels = new_rb_vels;
522        }
523
524        /*
525         * Update augmented mass matrix.
526         */
527        self.update_inertias(dt, bodies);
528    }
529
530    fn update_body_jacobians(&mut self) {
531        for i in 0..self.links.len() {
532            let link = &self.links[i];
533
534            if self.body_jacobians[i].ncols() != self.ndofs {
535                // TODO: use a resize instead.
536                self.body_jacobians[i] = Jacobian::zeros(self.ndofs);
537            }
538
539            let parent_to_world;
540
541            if i != 0 {
542                let parent_id = link.parent_internal_id;
543                let parent_link = &self.links[parent_id];
544                parent_to_world = parent_link.local_to_world;
545
546                let (link_j, parent_j) = self.body_jacobians.index_mut_const(i, parent_id);
547                link_j.copy_from(parent_j);
548
549                {
550                    let mut link_j_v = link_j.fixed_rows_mut::<DIM>(0);
551                    let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
552
553                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
554                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
555                }
556            } else {
557                self.body_jacobians[i].fill(0.0);
558                parent_to_world = Pose::IDENTITY;
559            }
560
561            let ndofs = link.joint.ndofs();
562            let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
563            let mut link_joint_j = tmp.columns_mut(0, ndofs);
564            let mut link_j_part = self.body_jacobians[i].columns_mut(link.assembly_id, ndofs);
565            link.joint.jacobian(
566                &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
567                &mut link_joint_j,
568            );
569            link_j_part += link_joint_j;
570
571            {
572                let link_j = &mut self.body_jacobians[i];
573                let (mut link_j_v, link_j_w) =
574                    link_j.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
575                let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
576                link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
577            }
578        }
579    }
580
581    fn update_inertias(&mut self, dt: Real, bodies: &RigidBodySet) {
582        if self.ndofs == 0 {
583            return; // Nothing to do.
584        }
585
586        if self.augmented_mass.ncols() != self.ndofs {
587            // TODO: do a resize instead of a full reallocation.
588            self.augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
589            self.acc_augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs);
590        } else {
591            self.augmented_mass.fill(0.0);
592            self.acc_augmented_mass.fill(0.0);
593        }
594
595        self.augmented_mass_indices.clear();
596
597        if self.coriolis_v.len() != self.links.len() {
598            self.coriolis_v.resize(
599                self.links.len(),
600                OMatrix::<Real, Dim, Dyn>::zeros(self.ndofs),
601            );
602            self.coriolis_w.resize(
603                self.links.len(),
604                OMatrix::<Real, AngDim, Dyn>::zeros(self.ndofs),
605            );
606            self.i_coriolis_dt = Jacobian::zeros(self.ndofs);
607        }
608
609        let mut curr_assembly_id = 0;
610
611        for i in 0..self.links.len() {
612            let link = &self.links[i];
613            let rb = &bodies[link.rigid_body];
614            let rb_mass = rb.mprops.effective_mass();
615            let rb_inertia = rb.mprops.effective_angular_inertia().into_matrix();
616            let body_jacobian = &self.body_jacobians[i];
617
618            // NOTE: the mass matrix index reordering operates on the assumption that the assembly
619            //       ids are traversed in order. This assert is here to ensure the assumption always
620            //       hold.
621            assert_eq!(
622                curr_assembly_id, link.assembly_id,
623                "Internal error: contiguity assumption on assembly_id does not hold."
624            );
625            curr_assembly_id += link.joint.ndofs();
626
627            if link.joint.kinematic {
628                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
629                    self.augmented_mass_indices.remove(k);
630                }
631            } else {
632                for k in link.assembly_id..link.assembly_id + link.joint.ndofs() {
633                    self.augmented_mass_indices.keep(k);
634                }
635            }
636
637            #[allow(unused_mut)] // mut is needed for 3D but not for 2D.
638            let mut augmented_inertia = rb_inertia;
639
640            #[cfg(feature = "dim3")]
641            {
642                // Derivative of gyroscopic forces.
643                let gyroscopic_matrix = rb.vels.angvel.gcross_matrix() * rb_inertia
644                    - (rb_inertia * rb.vels.angvel).gcross_matrix();
645
646                augmented_inertia += gyroscopic_matrix * dt;
647            }
648
649            // TODO: optimize that (knowing the structure of the augmented inertia matrix).
650            // TODO: this could be better optimized in 2D.
651            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
652            let rb_mass_matrix_wo_gyro = concat_rb_mass_matrix(rb_mass, rb_inertia.into());
653            #[allow(clippy::useless_conversion)] // Needed in 3D, no-op in 2D
654            let rb_mass_matrix = concat_rb_mass_matrix(rb_mass, augmented_inertia.into());
655            self.augmented_mass
656                .quadform(1.0, &rb_mass_matrix_wo_gyro, body_jacobian, 1.0);
657            self.acc_augmented_mass
658                .quadform(1.0, &rb_mass_matrix, body_jacobian, 1.0);
659
660            /*
661             *
662             * Coriolis matrix.
663             *
664             */
665            let rb_j = &self.body_jacobians[i];
666            let rb_j_w = rb_j.fixed_rows::<ANG_DIM>(DIM);
667
668            let ndofs = link.joint.ndofs();
669
670            if i != 0 {
671                let parent_id = link.parent_internal_id;
672                let parent_link = &self.links[parent_id];
673                let parent_rb = &bodies[parent_link.rigid_body];
674                let parent_j = &self.body_jacobians[parent_id];
675                let parent_j_w = parent_j.fixed_rows::<ANG_DIM>(DIM);
676                let parent_w = SimdAngVector::<Real>::from(parent_rb.vels.angvel).gcross_matrix();
677
678                let (coriolis_v, parent_coriolis_v) = self.coriolis_v.index_mut2(i, parent_id);
679                let (coriolis_w, parent_coriolis_w) = self.coriolis_w.index_mut2(i, parent_id);
680
681                coriolis_v.copy_from(parent_coriolis_v);
682                coriolis_w.copy_from(parent_coriolis_w);
683
684                // [c1 - c0].gcross() * (JDot + JDot/u * qdot)"
685                let shift_cross_tr = vect_to_na(link.shift02).gcross_matrix_tr();
686                coriolis_v.gemm(1.0, &shift_cross_tr, parent_coriolis_w, 1.0);
687
688                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
689                let dvel_cross = vect_to_na(
690                    rb.vels.angvel.gcross(link.shift02) + 2.0 * link.joint_velocity.linvel,
691                )
692                .gcross_matrix_tr();
693                coriolis_v.gemm(1.0, &dvel_cross, &parent_j_w, 1.0);
694
695                // JDot/u * qdot
696                coriolis_v.gemm(
697                    1.0,
698                    &vect_to_na(link.joint_velocity.linvel).gcross_matrix_tr(),
699                    &parent_j_w,
700                    1.0,
701                );
702                coriolis_v.gemm(1.0, &(parent_w * shift_cross_tr), &parent_j_w, 1.0);
703
704                #[cfg(feature = "dim3")]
705                {
706                    let vel_wrt_joint_w = vect_to_na(link.joint_velocity.angvel).gcross_matrix();
707                    coriolis_w.gemm(-1.0, &vel_wrt_joint_w, &parent_j_w, 1.0);
708                }
709
710                // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot)
711                if !link.joint.kinematic {
712                    let mut coriolis_v_part = coriolis_v.columns_mut(link.assembly_id, ndofs);
713
714                    let mut tmp1 = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
715                    let mut rb_joint_j = tmp1.columns_mut(0, ndofs);
716                    link.joint.jacobian(
717                        &(parent_link.local_to_world.rotation
718                            * link.joint.data.local_frame1.rotation),
719                        &mut rb_joint_j,
720                    );
721
722                    let rb_joint_j_v = rb_joint_j.fixed_rows::<DIM>(0);
723                    coriolis_v_part.gemm(2.0, &parent_w, &rb_joint_j_v, 1.0);
724
725                    #[cfg(feature = "dim3")]
726                    {
727                        let rb_joint_j_w = rb_joint_j.fixed_rows::<ANG_DIM>(DIM);
728                        let mut coriolis_w_part = coriolis_w.columns_mut(link.assembly_id, ndofs);
729                        coriolis_w_part.gemm(1.0, &parent_w, &rb_joint_j_w, 1.0);
730                    }
731                }
732            } else {
733                self.coriolis_v[i].fill(0.0);
734                self.coriolis_w[i].fill(0.0);
735            }
736
737            let coriolis_v = &mut self.coriolis_v[i];
738            let coriolis_w = &mut self.coriolis_w[i];
739
740            {
741                // [c3 - c2].gcross() * (JDot + JDot/u * qdot)
742                let shift_cross_tr = vect_to_na(link.shift23).gcross_matrix_tr();
743                coriolis_v.gemm(1.0, &shift_cross_tr, coriolis_w, 1.0);
744
745                // JDot
746                let dvel_cross = vect_to_na(rb.vels.angvel.gcross(link.shift23)).gcross_matrix_tr();
747                coriolis_v.gemm(1.0, &dvel_cross, &rb_j_w, 1.0);
748
749                // JDot/u * qdot
750                coriolis_v.gemm(
751                    1.0,
752                    &(SimdAngVector::<Real>::from(rb.vels.angvel).gcross_matrix() * shift_cross_tr),
753                    &rb_j_w,
754                    1.0,
755                );
756            }
757
758            let coriolis_v = &mut self.coriolis_v[i];
759            let coriolis_w = &mut self.coriolis_w[i];
760
761            /*
762             * Meld with the mass matrix.
763             */
764            {
765                let mut i_coriolis_dt_v = self.i_coriolis_dt.fixed_rows_mut::<DIM>(0);
766                i_coriolis_dt_v.copy_from(coriolis_v);
767                let rb_mass_dt = vect_to_na(rb_mass * dt);
768                i_coriolis_dt_v
769                    .column_iter_mut()
770                    .for_each(|mut c| c.component_mul_assign(&rb_mass_dt));
771            }
772
773            #[cfg(feature = "dim2")]
774            {
775                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
776                // NOTE: this is just an axpy, but on row columns.
777                i_coriolis_dt_w.zip_apply(coriolis_w, |o, x| *o = x * dt * rb_inertia);
778            }
779            #[cfg(feature = "dim3")]
780            {
781                let mut i_coriolis_dt_w = self.i_coriolis_dt.fixed_rows_mut::<ANG_DIM>(DIM);
782                i_coriolis_dt_w.gemm(dt, &mat_to_na(rb_inertia), coriolis_w, 0.0);
783            }
784
785            self.acc_augmented_mass
786                .gemm_tr(1.0, rb_j, &self.i_coriolis_dt, 1.0);
787        }
788
789        /*
790         * Damping.
791         */
792        for i in 0..self.ndofs {
793            self.acc_augmented_mass[(i, i)] += self.damping[i] * dt;
794            self.augmented_mass[(i, i)] += self.damping[i] * dt;
795        }
796
797        let effective_dim = self
798            .augmented_mass_indices
799            .dim_after_removal(self.acc_augmented_mass.nrows());
800
801        // PERF: since we clone the matrix anyway for LU, should be directly output
802        //       a new matrix instead of applying permutations?
803        self.augmented_mass_indices
804            .rearrange_columns(&mut self.acc_augmented_mass, true);
805        self.augmented_mass_indices
806            .rearrange_columns(&mut self.augmented_mass, true);
807
808        self.augmented_mass_indices
809            .rearrange_rows(&mut self.acc_augmented_mass, true);
810        self.augmented_mass_indices
811            .rearrange_rows(&mut self.augmented_mass, true);
812
813        // TODO: avoid allocation inside LU at each timestep.
814        self.acc_inv_augmented_mass = LU::new(
815            self.acc_augmented_mass
816                .view((0, 0), (effective_dim, effective_dim))
817                .into_owned(),
818        );
819        self.inv_augmented_mass = LU::new(
820            self.augmented_mass
821                .view((0, 0), (effective_dim, effective_dim))
822                .into_owned(),
823        );
824    }
825
826    /// The generalized velocity at the multibody_joint of the given link.
827    #[inline]
828    pub fn joint_velocity(&self, link: &MultibodyLink) -> DVectorView<'_, Real> {
829        let ndofs = link.joint().ndofs();
830        DVectorView::from_slice(
831            &self.velocities.as_slice()[link.assembly_id..link.assembly_id + ndofs],
832            ndofs,
833        )
834    }
835
836    /// The generalized accelerations of this multibodies.
837    #[inline]
838    pub fn generalized_acceleration(&self) -> DVectorView<'_, Real> {
839        self.accelerations.rows(0, self.ndofs)
840    }
841
842    /// The generalized velocities of this multibodies.
843    #[inline]
844    pub fn generalized_velocity(&self) -> DVectorView<'_, Real> {
845        self.velocities.rows(0, self.ndofs)
846    }
847
848    /// The body jacobian for link `link_id` calculated by the last call to [`Multibody::forward_kinematics`].
849    #[inline]
850    pub fn body_jacobian(&self, link_id: usize) -> &Jacobian<Real> {
851        &self.body_jacobians[link_id]
852    }
853
854    /// The mutable generalized velocities of this multibodies.
855    #[inline]
856    pub fn generalized_velocity_mut(&mut self) -> DVectorViewMut<'_, Real> {
857        self.velocities.rows_mut(0, self.ndofs)
858    }
859
860    #[inline]
861    pub(crate) fn integrate(&mut self, dt: Real) {
862        for rb in self.links.iter_mut() {
863            rb.joint
864                .integrate(dt, &self.velocities.as_slice()[rb.assembly_id..])
865        }
866    }
867
868    /// Apply displacements, in generalized coordinates, to this multibody.
869    ///
870    /// Note this does **not** updates the link poses, only their generalized coordinates.
871    /// To update the link poses and associated rigid-bodies, call [`Self::forward_kinematics`].
872    pub fn apply_displacements(&mut self, disp: &[Real]) {
873        for link in self.links.iter_mut() {
874            link.joint.apply_displacement(&disp[link.assembly_id..])
875        }
876    }
877
878    pub(crate) fn update_root_type(&mut self, bodies: &RigidBodySet, take_body_pose: bool) {
879        if let Some(rb) = bodies.get(self.links[0].rigid_body) {
880            if rb.is_dynamic() != self.root_is_dynamic {
881                let root_pose = if take_body_pose {
882                    *rb.position()
883                } else {
884                    self.links[0].local_to_world
885                };
886
887                if rb.is_dynamic() {
888                    let free_joint = MultibodyJoint::free(root_pose);
889                    let prev_root_ndofs = self.links[0].joint().ndofs();
890                    self.links[0].joint = free_joint;
891                    self.links[0].assembly_id = 0;
892                    self.ndofs += SPATIAL_DIM;
893
894                    self.velocities = self.velocities.clone().insert_rows(0, SPATIAL_DIM, 0.0);
895                    self.damping = self.damping.clone().insert_rows(0, SPATIAL_DIM, 0.0);
896                    self.accelerations =
897                        self.accelerations.clone().insert_rows(0, SPATIAL_DIM, 0.0);
898
899                    for link in &mut self.links[1..] {
900                        link.assembly_id += SPATIAL_DIM - prev_root_ndofs;
901                    }
902                } else {
903                    assert!(self.velocities.len() >= SPATIAL_DIM);
904                    assert!(self.damping.len() >= SPATIAL_DIM);
905                    assert!(self.accelerations.len() >= SPATIAL_DIM);
906
907                    let fixed_joint = MultibodyJoint::fixed(root_pose);
908                    let prev_root_ndofs = self.links[0].joint().ndofs();
909                    self.links[0].joint = fixed_joint;
910                    self.links[0].assembly_id = 0;
911                    self.ndofs -= prev_root_ndofs;
912
913                    if self.ndofs == 0 {
914                        self.velocities = DVector::zeros(0);
915                        self.damping = DVector::zeros(0);
916                        self.accelerations = DVector::zeros(0);
917                    } else {
918                        self.velocities =
919                            self.velocities.index((prev_root_ndofs.., 0)).into_owned();
920                        self.damping = self.damping.index((prev_root_ndofs.., 0)).into_owned();
921                        self.accelerations = self
922                            .accelerations
923                            .index((prev_root_ndofs.., 0))
924                            .into_owned();
925                    }
926
927                    for link in &mut self.links[1..] {
928                        link.assembly_id -= prev_root_ndofs;
929                    }
930                }
931
932                self.root_is_dynamic = rb.is_dynamic();
933            }
934
935            // Make sure the positions are properly set to match the rigid-body’s.
936            if take_body_pose {
937                if self.links[0].joint.data.locked_axes.is_empty() {
938                    self.links[0].joint.set_free_pos(*rb.position());
939                } else {
940                    self.links[0].joint.data.local_frame1 = *rb.position();
941                }
942            }
943        }
944    }
945
946    /// Update the rigid-body poses based on this multibody joint poses.
947    ///
948    /// This is typically called after [`Self::forward_kinematics`] to apply the new joint poses
949    /// to the rigid-bodies.
950    pub fn update_rigid_bodies(&self, bodies: &mut RigidBodySet, update_mass_properties: bool) {
951        self.update_rigid_bodies_internal(bodies, update_mass_properties, false, true)
952    }
953
954    pub(crate) fn update_rigid_bodies_internal(
955        &self,
956        bodies: &mut RigidBodySet,
957        update_mass_properties: bool,
958        update_next_positions_only: bool,
959        change_tracking: bool,
960    ) {
961        // Handle the children. They all have a parent within this multibody.
962        for link in self.links.iter() {
963            let rb = if change_tracking {
964                bodies.get_mut_internal_with_modification_tracking(link.rigid_body)
965            } else {
966                bodies.get_mut_internal(link.rigid_body)
967            };
968
969            if let Some(rb) = rb {
970                rb.pos.next_position = link.local_to_world;
971
972                if !update_next_positions_only {
973                    rb.pos.position = link.local_to_world;
974                }
975
976                if update_mass_properties {
977                    rb.mprops
978                        .update_world_mass_properties(rb.body_type, &link.local_to_world);
979                }
980            }
981        }
982    }
983
984    // TODO: make a version that doesn’t write back to bodies and doesn’t update the jacobians
985    //       (i.e. just something used by the velocity solver’s small steps).
986    /// Apply forward-kinematics to this multibody.
987    ///
988    /// This will update the [`MultibodyLink`] pose information as wall as the body jacobians.
989    /// This will also ensure that the multibody has the proper number of degrees of freedom if
990    /// its root node changed between dynamic and non-dynamic.
991    ///
992    /// Note that this does **not** update the poses of the [`RigidBody`] attached to the joints.
993    /// Run [`Self::update_rigid_bodies`] to trigger that update.
994    ///
995    /// This method updates `self` with the result of the forward-kinematics operation.
996    /// For a non-mutable version running forward kinematics on a single link, see
997    /// [`Self::forward_kinematics_single_link`].
998    ///
999    /// ## Parameters
1000    /// - `bodies`: the set of rigid-bodies.
1001    /// - `read_root_pose_from_rigid_body`: if set to `true`, the root joint (either a fixed joint,
1002    ///   or a free joint) will have its pose set to its associated-rigid-body pose. Set this to `true`
1003    ///   when the root rigid-body pose has been modified and needs to affect the multibody.
1004    pub fn forward_kinematics(
1005        &mut self,
1006        bodies: &RigidBodySet,
1007        read_root_pose_from_rigid_body: bool,
1008    ) {
1009        // Be sure the degrees of freedom match and take the root position if needed.
1010        self.update_root_type(bodies, read_root_pose_from_rigid_body);
1011
1012        // Special case for the root, which has no parent.
1013        {
1014            let link = &mut self.links[0];
1015            link.local_to_parent = link.joint.body_to_parent();
1016            link.local_to_world = link.local_to_parent;
1017        }
1018
1019        // Handle the children. They all have a parent within this multibody.
1020        for i in 1..self.links.len() {
1021            let (link, parent_link) = self.links.get_mut_with_parent(i);
1022
1023            link.local_to_parent = link.joint.body_to_parent();
1024            link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1025
1026            {
1027                let parent_rb = &bodies[parent_link.rigid_body];
1028                let link_rb = &bodies[link.rigid_body];
1029                let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1030                let c2 =
1031                    link.local_to_world * Vector::from(link.joint.data.local_frame2.translation);
1032                let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1033
1034                link.shift02 = c2 - c0;
1035                link.shift23 = c3 - c2;
1036            }
1037
1038            assert_eq!(
1039                bodies[link.rigid_body].body_type,
1040                RigidBodyType::Dynamic,
1041                "A rigid-body that is not at the root of a multibody must be dynamic."
1042            );
1043        }
1044
1045        /*
1046         * Compute body jacobians.
1047         */
1048        self.update_body_jacobians();
1049    }
1050
1051    /// Computes the ids of all the links between the root and the link identified by `link_id`.
1052    pub fn kinematic_branch(&self, link_id: usize) -> Vec<usize> {
1053        let mut branch = vec![]; // Perf: avoid allocation.
1054        let mut curr_id = Some(link_id);
1055
1056        while let Some(id) = curr_id {
1057            branch.push(id);
1058            curr_id = self.links[id].parent_id();
1059        }
1060
1061        branch.reverse();
1062        branch
1063    }
1064
1065    /// Apply forward-kinematics to compute the position of a single link of this multibody.
1066    ///
1067    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this link.
1068    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1069    /// is the sum of the current position of `self` and this `displacement`.
1070    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1071    //       that we are only traversing a single kinematic chain. Could this be refactored?
1072    pub fn forward_kinematics_single_link(
1073        &self,
1074        bodies: &RigidBodySet,
1075        link_id: usize,
1076        displacement: Option<&[Real]>,
1077        out_jacobian: Option<&mut Jacobian<Real>>,
1078    ) -> Pose {
1079        let branch = self.kinematic_branch(link_id);
1080        self.forward_kinematics_single_branch(bodies, &branch, displacement, out_jacobian)
1081    }
1082
1083    /// Apply forward-kinematics to compute the position of a single sorted branch of this multibody.
1084    ///
1085    /// The given `branch` must have the following properties:
1086    /// - It must be sorted, i.e., `branch[i] < branch[i + 1]`.
1087    /// - All the indices must be part of the same kinematic branch.
1088    /// - If a link is `branch[i]`, then `branch[i - 1]` must be its parent.
1089    ///
1090    /// In general, this method shouldn’t be used directly and [`Self::forward_kinematics_single_link`]
1091    /// should be preferred since it computes the branch indices automatically.
1092    ///
1093    /// If you want to calculate the branch indices manually, see [`Self::kinematic_branch`].
1094    ///
1095    /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this branch.
1096    /// This represents the body jacobian for the last link in the branch.
1097    ///
1098    /// If `displacement` is `Some`, the generalized position considered during transform propagation
1099    /// is the sum of the current position of `self` and this `displacement`.
1100    // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
1101    //       that we are only traversing a single kinematic chain. Could this be refactored?
1102    #[profiling::function]
1103    pub fn forward_kinematics_single_branch(
1104        &self,
1105        bodies: &RigidBodySet,
1106        branch: &[usize],
1107        displacement: Option<&[Real]>,
1108        mut out_jacobian: Option<&mut Jacobian<Real>>,
1109    ) -> Pose {
1110        if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1111            if out_jacobian.ncols() != self.ndofs {
1112                *out_jacobian = Jacobian::zeros(self.ndofs);
1113            } else {
1114                out_jacobian.fill(0.0);
1115            }
1116        }
1117
1118        let mut parent_link: Option<MultibodyLink> = None;
1119
1120        for i in branch {
1121            let mut link = self.links[*i];
1122
1123            if let Some(displacement) = displacement {
1124                link.joint
1125                    .apply_displacement(&displacement[link.assembly_id..]);
1126            }
1127
1128            let parent_to_world;
1129
1130            if let Some(parent_link) = parent_link {
1131                link.local_to_parent = link.joint.body_to_parent();
1132                link.local_to_world = parent_link.local_to_world * link.local_to_parent;
1133
1134                {
1135                    let parent_rb = &bodies[parent_link.rigid_body];
1136                    let link_rb = &bodies[link.rigid_body];
1137                    let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
1138                    let c2 = link.local_to_world
1139                        * Vector::from(link.joint.data.local_frame2.translation);
1140                    let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
1141
1142                    link.shift02 = c2 - c0;
1143                    link.shift23 = c3 - c2;
1144                }
1145
1146                parent_to_world = parent_link.local_to_world;
1147
1148                if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1149                    let (mut link_j_v, parent_j_w) =
1150                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1151                    let shift_tr = vect_to_na(link.shift02).gcross_matrix_tr();
1152                    link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
1153                }
1154            } else {
1155                link.local_to_parent = link.joint.body_to_parent();
1156                link.local_to_world = link.local_to_parent;
1157                parent_to_world = Pose::IDENTITY;
1158            }
1159
1160            if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
1161                let ndofs = link.joint.ndofs();
1162                let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
1163                let mut link_joint_j = tmp.columns_mut(0, ndofs);
1164                let mut link_j_part = out_jacobian.columns_mut(link.assembly_id, ndofs);
1165                link.joint.jacobian(
1166                    &(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
1167                    &mut link_joint_j,
1168                );
1169                link_j_part += link_joint_j;
1170
1171                {
1172                    let (mut link_j_v, link_j_w) =
1173                        out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
1174                    let shift_tr = vect_to_na(link.shift23).gcross_matrix_tr();
1175                    link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
1176                }
1177            }
1178
1179            parent_link = Some(link);
1180        }
1181
1182        parent_link
1183            .map(|link| link.local_to_world)
1184            .unwrap_or(Pose::IDENTITY)
1185    }
1186
1187    /// The total number of freedoms of this multibody.
1188    #[inline]
1189    pub fn ndofs(&self) -> usize {
1190        self.ndofs
1191    }
1192
1193    pub(crate) fn fill_jacobians(
1194        &self,
1195        link_id: usize,
1196        unit_force: Vector,
1197        unit_torque: AngVector,
1198        j_id: &mut usize,
1199        jacobians: &mut DVector,
1200    ) -> (Real, Real) {
1201        if self.ndofs == 0 {
1202            return (0.0, 0.0);
1203        }
1204
1205        let wj_id = *j_id + self.ndofs;
1206        let force = Force {
1207            linear: unit_force,
1208            #[cfg(feature = "dim2")]
1209            angular: unit_torque,
1210            #[cfg(feature = "dim3")]
1211            angular: unit_torque,
1212        };
1213
1214        let link = &self.links[link_id];
1215        let mut out_j = jacobians.rows_mut(*j_id, self.ndofs);
1216        self.body_jacobians[link.internal_id].tr_mul_to(force.as_vector(), &mut out_j);
1217
1218        // TODO: Optimize with a copy_nonoverlapping?
1219        for i in 0..self.ndofs {
1220            jacobians[wj_id + i] = jacobians[*j_id + i];
1221        }
1222
1223        {
1224            let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs);
1225            self.augmented_mass_indices
1226                .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| {
1227                    self.inv_augmented_mass.solve_mut(out_invm_j);
1228                });
1229        }
1230
1231        let j = jacobians.rows(*j_id, self.ndofs);
1232        let invm_j = jacobians.rows(wj_id, self.ndofs);
1233        *j_id += self.ndofs * 2;
1234
1235        (j.dot(&invm_j), j.dot(&self.generalized_velocity()))
1236    }
1237
1238    // #[cfg(feature = "parallel")]
1239    // #[inline]
1240    // pub(crate) fn has_active_internal_constraints(&self) -> bool {
1241    //     self.links()
1242    //         .any(|link| link.joint().num_velocity_constraints() != 0)
1243    // }
1244
1245    #[cfg(feature = "parallel")]
1246    #[inline]
1247    #[allow(dead_code)] // That will likely be useful when we re-introduce intra-island parallelism.
1248    pub(crate) fn num_active_internal_constraints_and_jacobian_lines(&self) -> (usize, usize) {
1249        let num_constraints: usize = self
1250            .links
1251            .iter()
1252            .map(|l| l.joint().num_velocity_constraints())
1253            .sum();
1254        (num_constraints, num_constraints)
1255    }
1256}
1257
1258#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
1259#[derive(Clone, Debug)]
1260struct IndexSequence {
1261    first_to_remove: usize,
1262    index_map: Vec<usize>,
1263}
1264
1265impl IndexSequence {
1266    fn new() -> Self {
1267        Self {
1268            first_to_remove: usize::MAX,
1269            index_map: vec![],
1270        }
1271    }
1272
1273    fn clear(&mut self) {
1274        self.first_to_remove = usize::MAX;
1275        self.index_map.clear();
1276    }
1277
1278    fn keep(&mut self, i: usize) {
1279        if self.first_to_remove == usize::MAX {
1280            // Nothing got removed yet. No need to register any
1281            // special indexing.
1282            return;
1283        }
1284
1285        self.index_map.push(i);
1286    }
1287
1288    fn remove(&mut self, i: usize) {
1289        if self.first_to_remove == usize::MAX {
1290            self.first_to_remove = i;
1291        }
1292    }
1293
1294    fn dim_after_removal(&self, original_dim: usize) -> usize {
1295        if self.first_to_remove == usize::MAX {
1296            original_dim
1297        } else {
1298            self.first_to_remove + self.index_map.len()
1299        }
1300    }
1301
1302    fn rearrange_columns<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1303        &self,
1304        mat: &mut na::Matrix<Real, R, C, S>,
1305        clear_removed: bool,
1306    ) {
1307        if self.first_to_remove == usize::MAX {
1308            // Nothing to rearrange.
1309            return;
1310        }
1311
1312        for (target_shift, source) in self.index_map.iter().enumerate() {
1313            let target = self.first_to_remove + target_shift;
1314            let (mut target_col, source_col) = mat.columns_range_pair_mut(target, *source);
1315            target_col.copy_from(&source_col);
1316        }
1317
1318        if clear_removed {
1319            mat.columns_range_mut(self.first_to_remove + self.index_map.len()..)
1320                .fill(0.0);
1321        }
1322    }
1323
1324    fn rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1325        &self,
1326        mat: &mut na::Matrix<Real, R, C, S>,
1327        clear_removed: bool,
1328    ) {
1329        if self.first_to_remove == usize::MAX {
1330            // Nothing to rearrange.
1331            return;
1332        }
1333
1334        for mut col in mat.column_iter_mut() {
1335            for (target_shift, source) in self.index_map.iter().enumerate() {
1336                let target = self.first_to_remove + target_shift;
1337                col[target] = col[*source];
1338            }
1339
1340            if clear_removed {
1341                col.rows_range_mut(self.first_to_remove + self.index_map.len()..)
1342                    .fill(0.0);
1343            }
1344        }
1345    }
1346
1347    fn inv_rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>(
1348        &self,
1349        mat: &mut na::Matrix<Real, R, C, S>,
1350    ) {
1351        if self.first_to_remove == usize::MAX {
1352            // Nothing to rearrange.
1353            return;
1354        }
1355
1356        for mut col in mat.column_iter_mut() {
1357            for (target_shift, source) in self.index_map.iter().enumerate().rev() {
1358                let target = self.first_to_remove + target_shift;
1359                col[*source] = col[target];
1360                col[target] = 0.0;
1361            }
1362        }
1363    }
1364
1365    fn with_rearranged_rows_mut<C: na::Dim, S: StorageMut<Real, Dyn, C>>(
1366        &self,
1367        mat: &mut na::Matrix<Real, Dyn, C, S>,
1368        mut f: impl FnMut(&mut na::MatrixViewMut<Real, Dyn, C, S::RStride, S::CStride>),
1369    ) {
1370        self.rearrange_rows(mat, true);
1371        let effective_dim = self.dim_after_removal(mat.nrows());
1372        if effective_dim > 0 {
1373            f(&mut mat.rows_mut(0, effective_dim));
1374        }
1375        self.inv_rearrange_rows(mat);
1376    }
1377}
1378
1379#[cfg(test)]
1380mod test {
1381    use super::IndexSequence;
1382    use crate::dynamics::{ImpulseJointSet, IslandManager};
1383    #[cfg(feature = "dim3")]
1384    use crate::math::Vector;
1385    use crate::math::{Real, SPATIAL_DIM};
1386    use crate::prelude::{
1387        ColliderSet, MultibodyJointHandle, MultibodyJointSet, RevoluteJoint, RigidBodyBuilder,
1388        RigidBodySet,
1389    };
1390    use na::{DVector, RowDVector};
1391
1392    #[test]
1393    fn test_multibody_append() {
1394        let mut bodies = RigidBodySet::new();
1395        let mut joints = MultibodyJointSet::new();
1396
1397        let a = bodies.insert(RigidBodyBuilder::dynamic());
1398        let b = bodies.insert(RigidBodyBuilder::dynamic());
1399        let c = bodies.insert(RigidBodyBuilder::dynamic());
1400        let d = bodies.insert(RigidBodyBuilder::dynamic());
1401
1402        #[cfg(feature = "dim2")]
1403        let joint = RevoluteJoint::new();
1404        #[cfg(feature = "dim3")]
1405        let joint = RevoluteJoint::new(Vector::X);
1406
1407        let mb_handle = joints.insert(a, b, joint, true).unwrap();
1408        joints.insert(c, d, joint, true).unwrap();
1409        joints.insert(b, c, joint, true).unwrap();
1410
1411        assert_eq!(joints.get(mb_handle).unwrap().0.ndofs, SPATIAL_DIM + 3);
1412    }
1413
1414    #[test]
1415    fn test_multibody_insert() {
1416        let mut rnd = oorandom::Rand32::new(1234);
1417
1418        for k in 0..10 {
1419            let mut bodies = RigidBodySet::new();
1420            let mut multibody_joints = MultibodyJointSet::new();
1421
1422            let num_links = 100;
1423            let mut handles = vec![];
1424
1425            for _ in 0..num_links {
1426                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1427            }
1428
1429            let mut insertion_id: Vec<_> = (0..num_links - 1).collect();
1430
1431            #[cfg(feature = "dim2")]
1432            let joint = RevoluteJoint::new();
1433            #[cfg(feature = "dim3")]
1434            let joint = RevoluteJoint::new(Vector::X);
1435
1436            match k {
1437                0 => {} // Remove in insertion order.
1438                1 => {
1439                    // Remove from leaf to root.
1440                    insertion_id.reverse();
1441                }
1442                _ => {
1443                    // Shuffle the vector a bit.
1444                    // (This test checks multiple shuffle arrangements due to k > 2).
1445                    for l in 0..num_links - 1 {
1446                        insertion_id.swap(l, rnd.rand_range(0..num_links as u32 - 1) as usize);
1447                    }
1448                }
1449            }
1450
1451            let mut mb_handle = MultibodyJointHandle::invalid();
1452            for i in insertion_id {
1453                mb_handle = multibody_joints
1454                    .insert(handles[i], handles[i + 1], joint, true)
1455                    .unwrap();
1456            }
1457
1458            assert_eq!(
1459                multibody_joints.get(mb_handle).unwrap().0.ndofs,
1460                SPATIAL_DIM + num_links - 1
1461            );
1462        }
1463    }
1464
1465    #[test]
1466    fn test_multibody_remove() {
1467        let mut rnd = oorandom::Rand32::new(1234);
1468
1469        for k in 0..10 {
1470            let mut bodies = RigidBodySet::new();
1471            let mut multibody_joints = MultibodyJointSet::new();
1472            let mut colliders = ColliderSet::new();
1473            let mut impulse_joints = ImpulseJointSet::new();
1474            let mut islands = IslandManager::new();
1475
1476            let num_links = 100;
1477            let mut handles = vec![];
1478
1479            for _ in 0..num_links {
1480                handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
1481            }
1482
1483            #[cfg(feature = "dim2")]
1484            let joint = RevoluteJoint::new();
1485            #[cfg(feature = "dim3")]
1486            let joint = RevoluteJoint::new(Vector::X);
1487
1488            for i in 0..num_links - 1 {
1489                multibody_joints
1490                    .insert(handles[i], handles[i + 1], joint, true)
1491                    .unwrap();
1492            }
1493
1494            match k {
1495                0 => {} // Remove in insertion order.
1496                1 => {
1497                    // Remove from leaf to root.
1498                    handles.reverse();
1499                }
1500                _ => {
1501                    // Shuffle the vector a bit.
1502                    // (This test checks multiple shuffle arrangements due to k > 2).
1503                    for l in 0..num_links {
1504                        handles.swap(l, rnd.rand_range(0..num_links as u32) as usize);
1505                    }
1506                }
1507            }
1508
1509            for handle in handles {
1510                bodies.remove(
1511                    handle,
1512                    &mut islands,
1513                    &mut colliders,
1514                    &mut impulse_joints,
1515                    &mut multibody_joints,
1516                    true,
1517                );
1518            }
1519        }
1520    }
1521
1522    fn test_sequence() -> IndexSequence {
1523        let mut seq = IndexSequence::new();
1524        seq.remove(2);
1525        seq.remove(3);
1526        seq.remove(4);
1527        seq.keep(5);
1528        seq.keep(6);
1529        seq.remove(7);
1530        seq.keep(8);
1531        seq
1532    }
1533
1534    #[test]
1535    fn index_sequence_rearrange_columns() {
1536        let seq = test_sequence();
1537        let mut vec = RowDVector::from_fn(10, |_, c| c as Real);
1538        seq.rearrange_columns(&mut vec, true);
1539        assert_eq!(
1540            vec,
1541            RowDVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
1542        );
1543    }
1544
1545    #[test]
1546    fn index_sequence_rearrange_rows() {
1547        let seq = test_sequence();
1548        let mut vec = DVector::from_fn(10, |r, _| r as Real);
1549        seq.rearrange_rows(&mut vec, true);
1550        assert_eq!(
1551            vec,
1552            DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0])
1553        );
1554        seq.inv_rearrange_rows(&mut vec);
1555        assert_eq!(
1556            vec,
1557            DVector::from(vec![0.0, 1.0, 0.0, 0.0, 0.0, 5.0, 6.0, 0.0, 8.0, 0.0])
1558        );
1559    }
1560
1561    #[test]
1562    fn index_sequence_with_rearranged_rows_mut() {
1563        let seq = test_sequence();
1564        let mut vec = DVector::from_fn(10, |r, _| r as Real);
1565        seq.with_rearranged_rows_mut(&mut vec, |v| {
1566            assert_eq!(v.len(), 5);
1567            assert_eq!(*v, DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0]));
1568            *v *= 10.0;
1569        });
1570        assert_eq!(
1571            vec,
1572            DVector::from(vec![0.0, 10.0, 0.0, 0.0, 0.0, 50.0, 60.0, 0.0, 80.0, 0.0])
1573        );
1574    }
1575}