diff options
| author | Sébastien Crozet <sebcrozet@dimforge.com> | 2024-06-02 19:24:18 +0200 |
|---|---|---|
| committer | Sébastien Crozet <sebastien@crozet.re> | 2024-06-09 12:09:58 +0200 |
| commit | 0bdc6202075e4fa9204c7fe1e3f553ab11abf3dd (patch) | |
| tree | 1adeeaa17f7f3f38a2f9ef7d020c5590f0068376 /src/dynamics | |
| parent | d9585de20bc158006e4669c251d5d8b3edfbf188 (diff) | |
| download | rapier-0bdc6202075e4fa9204c7fe1e3f553ab11abf3dd.tar.gz rapier-0bdc6202075e4fa9204c7fe1e3f553ab11abf3dd.tar.bz2 rapier-0bdc6202075e4fa9204c7fe1e3f553ab11abf3dd.zip | |
feat: add suport for kinematic multibody links
Diffstat (limited to 'src/dynamics')
| -rw-r--r-- | src/dynamics/joint/multibody_joint/multibody.rs | 325 | ||||
| -rw-r--r-- | src/dynamics/joint/multibody_joint/multibody_joint.rs | 11 | ||||
| -rw-r--r-- | src/dynamics/joint/multibody_joint/multibody_joint_set.rs | 33 |
3 files changed, 336 insertions, 33 deletions
diff --git a/src/dynamics/joint/multibody_joint/multibody.rs b/src/dynamics/joint/multibody_joint/multibody.rs index 2088148..352f289 100644 --- a/src/dynamics/joint/multibody_joint/multibody.rs +++ b/src/dynamics/joint/multibody_joint/multibody.rs @@ -8,7 +8,10 @@ use crate::math::{ }; use crate::prelude::MultibodyJoint; use crate::utils::{IndexMut2, SimdAngularInertia, SimdCross, SimdCrossMatrix}; -use na::{self, DMatrix, DVector, DVectorView, DVectorViewMut, Dyn, OMatrix, SMatrix, SVector, LU}; +use na::{ + self, DMatrix, DVector, DVectorView, DVectorViewMut, Dyn, OMatrix, SMatrix, SVector, + StorageMut, LU, +}; #[repr(C)] #[derive(Copy, Clone, Debug, Default)] @@ -59,15 +62,21 @@ fn concat_rb_mass_matrix( #[derive(Clone)] pub struct Multibody { // TODO: serialization: skip the workspace fields. - links: MultibodyLinkVec, + pub(crate) links: MultibodyLinkVec, pub(crate) velocities: DVector<Real>, pub(crate) damping: DVector<Real>, pub(crate) accelerations: DVector<Real>, body_jacobians: Vec<Jacobian<Real>>, + // NOTE: the mass matrices are dimentionned based on the non-kinematic degrees of + // freedoms only. The `Self::augmented_mass_permutation` sequence can be used to + // move dofs from/to a format that matches the augmented mass. // TODO: use sparse matrices? augmented_mass: DMatrix<Real>, inv_augmented_mass: LU<Real, Dyn, Dyn>, + // The indexing sequence for moving all kinematics degrees of + // freedoms to the end of the generalized coordinates vector. + augmented_mass_indices: IndexSequence, acc_augmented_mass: DMatrix<Real>, acc_inv_augmented_mass: LU<Real, Dyn, Dyn>, @@ -321,7 +330,7 @@ impl Multibody { pub(crate) fn add_link( &mut self, - parent: Option<usize>, // FIXME: should be a RigidBodyHandle? + parent: Option<usize>, // TODO: should be a RigidBodyHandle? dof: MultibodyJoint, body: RigidBodyHandle, ) -> &mut MultibodyLink { @@ -459,8 +468,10 @@ impl Multibody { self.accelerations .cmpy(-1.0, &self.damping, &self.velocities, 1.0); - self.acc_inv_augmented_mass - .solve_mut(&mut self.accelerations); + self.augmented_mass_indices + .with_rearranged_rows_mut(&mut self.accelerations, |accs| { + self.acc_inv_augmented_mass.solve_mut(accs); + }); } /// Computes the constant terms of the dynamics. @@ -507,7 +518,7 @@ impl Multibody { let link = &self.links[i]; if self.body_jacobians[i].ncols() != self.ndofs { - // FIXME: use a resize instead. + // TODO: use a resize instead. self.body_jacobians[i] = Jacobian::zeros(self.ndofs); } @@ -558,6 +569,8 @@ impl Multibody { return; // Nothing to do. } + let mut kinematic_ndofs = 0; + if self.augmented_mass.ncols() != self.ndofs { // TODO: do a resize instead of a full reallocation. self.augmented_mass = DMatrix::zeros(self.ndofs, self.ndofs); @@ -567,6 +580,8 @@ impl Multibody { self.acc_augmented_mass.fill(0.0); } + self.augmented_mass_indices.clear(); + if self.coriolis_v.len() != self.links.len() { self.coriolis_v.resize( self.links.len(), @@ -579,14 +594,34 @@ impl Multibody { self.i_coriolis_dt = Jacobian::zeros(self.ndofs); } + let mut curr_assembly_id = 0; + for i in 0..self.links.len() { let link = &self.links[i]; let rb = &bodies[link.rigid_body]; let rb_mass = rb.mprops.effective_mass(); let rb_inertia = rb.mprops.effective_angular_inertia().into_matrix(); - let body_jacobian = &self.body_jacobians[i]; + // NOTE: the mass matrix index reordering operates on the assumption that the assembly + // ids are traversed in order. This assert is here to ensure the assumption always + // hold. + assert_eq!( + curr_assembly_id, link.assembly_id, + "Internal error: contiguity assumption on assembly_id does not hold." + ); + curr_assembly_id += link.joint.ndofs(); + + if link.joint.kinematic { + for k in link.assembly_id..link.assembly_id + link.joint.ndofs() { + self.augmented_mass_indices.remove(k); + } + } else { + for k in link.assembly_id..link.assembly_id + link.joint.ndofs() { + self.augmented_mass_indices.keep(k); + } + } + #[allow(unused_mut)] // mut is needed for 3D but not for 2D. let mut augmented_inertia = rb_inertia; @@ -658,7 +693,7 @@ impl Multibody { } // JDot (but the 2.0 originates from the sum of two identical terms in JDot and JDot/u * gdot) - { + if !link.joint.kinematic { let mut coriolis_v_part = coriolis_v.columns_mut(link.assembly_id, ndofs); let mut tmp1 = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros(); @@ -743,12 +778,33 @@ impl Multibody { self.augmented_mass[(i, i)] += self.damping[i] * dt; } - // FIXME: avoid allocation inside LU at each timestep. - self.acc_inv_augmented_mass = LU::new(self.acc_augmented_mass.clone()); - self.inv_augmented_mass = LU::new(self.augmented_mass.clone()); - // self.acc_inv_augmented_mass = self.inv_augmented_mass.clone(); - // self.augmented_mass = self.acc_augmented_mass.clone(); - // self.inv_augmented_mass = self.acc_inv_augmented_mass.clone(); + let effective_dim = self + .augmented_mass_indices + .dim_after_removal(self.acc_augmented_mass.nrows()); + + // PERF: since we clone the matrix anyway for LU, should be directly output + // a new matrix instead of applying permutations? + self.augmented_mass_indices + .rearrange_columns(&mut self.acc_augmented_mass, true); + self.augmented_mass_indices + .rearrange_columns(&mut self.augmented_mass, true); + + self.augmented_mass_indices + .rearrange_rows(&mut self.acc_augmented_mass, true); + self.augmented_mass_indices + .rearrange_rows(&mut self.augmented_mass, true); + + // TODO: avoid allocation inside LU at each timestep. + self.acc_inv_augmented_mass = LU::new( + self.acc_augmented_mass + .view((0, 0), (effective_dim, effective_dim)) + .into_owned(), + ); + self.inv_augmented_mass = LU::new( + self.augmented_mass + .view((0, 0), (effective_dim, effective_dim)) + .into_owned(), + ); } /// The generalized velocity at the multibody_joint of the given link. @@ -976,6 +1032,20 @@ impl Multibody { self.update_body_jacobians(); } + /// Computes the ids of all the linkes betheen the root and the link identified by `link_id`. + pub fn kinematic_branch(&self, link_id: usize) -> Vec<usize> { + let mut branch = vec![]; // Perf: avoid allocation. + let mut curr_id = Some(link_id); + + while let Some(id) = curr_id { + branch.push(id); + curr_id = self.links[id].parent_id(); + } + + branch.reverse(); + branch + } + /// Apply forward-kinematics to compute the position of a single link of this multibody. /// /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this link. @@ -990,16 +1060,36 @@ impl Multibody { displacement: Option<&[Real]>, mut out_jacobian: Option<&mut Jacobian<Real>>, ) -> Isometry<Real> { - let mut branch = vec![]; // Perf: avoid allocation. - let mut curr_id = Some(link_id); - - while let Some(id) = curr_id { - branch.push(id); - curr_id = self.links[id].parent_id(); - } - - branch.reverse(); + let branch = self.kinematic_branch(link_id); + self.forward_kinematics_single_branch(bodies, &branch, displacement, out_jacobian) + } + /// Apply forward-kinematics to compute the position of a single sorted branch of this multibody. + /// + /// The given `branch` must have the following properties: + /// - It must be sorted, i.e., `branch[i] < branch[i + 1]`. + /// - All the indices must be part of the same kinematic branch. + /// - If a link is `branch[i]`, then `branch[i - 1]` must be its parent. + /// + /// In general, this method shouldn’t be used directly and [`Self::forward_kinematics_single_link`̦] + /// should be preferred since it computes the branch indices automatically. + /// + /// If you watn to calculate the branch indices manually, see [`Self::kinematic_branch`]. + /// + /// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this branch. + /// This represents the body jacobian for the last link in the branch. + /// + /// If `displacement` is `Some`, the generalized position considered during transform propagation + /// is the sum of the current position of `self` and this `displacement`. + // TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except + // that we are only traversing a single kinematic chain. Could this be refactored? + pub fn forward_kinematics_single_branch( + &self, + bodies: &RigidBodySet, + branch: &[usize], + displacement: Option<&[Real]>, + mut out_jacobian: Option<&mut Jacobian<Real>>, + ) -> Isometry<Real> { if let Some(out_jacobian) = out_jacobian.as_deref_mut() { if out_jacobian.ncols() != self.ndofs { *out_jacobian = Jacobian::zeros(self.ndofs); @@ -1011,7 +1101,7 @@ impl Multibody { let mut parent_link: Option<MultibodyLink> = None; for i in branch { - let mut link = self.links[i]; + let mut link = self.links[*i]; if let Some(displacement) = displacement { link.joint @@ -1115,7 +1205,10 @@ impl Multibody { { let mut out_invm_j = jacobians.rows_mut(wj_id, self.ndofs); - self.inv_augmented_mass.solve_mut(&mut out_invm_j); + self.augmented_mass_indices + .with_rearranged_rows_mut(&mut out_invm_j, |out_invm_j| { + self.inv_augmented_mass.solve_mut(out_invm_j); + }); } let j = jacobians.rows(*j_id, self.ndofs); @@ -1144,3 +1237,185 @@ impl Multibody { (num_constraints, num_constraints) } } + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +struct IndexSequence { + first_to_remove: usize, + index_map: Vec<usize>, +} + +impl IndexSequence { + fn new() -> Self { + Self { + first_to_remove: usize::MAX, + index_map: vec![], + } + } + + fn clear(&mut self) { + self.first_to_remove = usize::MAX; + self.index_map.clear(); + } + + fn keep(&mut self, i: usize) { + if self.first_to_remove == usize::MAX { + // Nothing got removed yet. No need to register any + // special indexing. + return; + } + + self.index_map.push(i); + } + + fn remove(&mut self, i: usize) { + if self.first_to_remove == usize::MAX { + self.first_to_remove = i; + } + } + + fn dim_after_removal(&self, original_dim: usize) -> usize { + if self.first_to_remove == usize::MAX { + original_dim + } else { + self.first_to_remove + self.index_map.len() + } + } + + fn rearrange_columns<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>( + &self, + mat: &mut na::Matrix<Real, R, C, S>, + clear_removed: bool, + ) { + if self.first_to_remove == usize::MAX { + // Nothing to rearrange. + return; + } + + for (target_shift, source) in self.index_map.iter().enumerate() { + let target = self.first_to_remove + target_shift; + let (mut target_col, source_col) = mat.columns_range_pair_mut(target, *source); + target_col.copy_from(&source_col); + } + + if clear_removed { + mat.columns_range_mut(self.first_to_remove + self.index_map.len()..) + .fill(0.0); + } + } + + fn rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>( + &self, + mat: &mut na::Matrix<Real, R, C, S>, + clear_removed: bool, + ) { + if self.first_to_remove == usize::MAX { + // Nothing to rearrange. + return; + } + + for mut col in mat.column_iter_mut() { + for (target_shift, source) in self.index_map.iter().enumerate() { + let target = self.first_to_remove + target_shift; + col[target] = col[*source]; + } + + if clear_removed { + col.rows_range_mut(self.first_to_remove + self.index_map.len()..) + .fill(0.0); + } + } + } + + fn inv_rearrange_rows<R: na::Dim, C: na::Dim, S: StorageMut<Real, R, C>>( + &self, + mat: &mut na::Matrix<Real, R, C, S>, + ) { + if self.first_to_remove == usize::MAX { + // Nothing to rearrange. + return; + } + + for mut col in mat.column_iter_mut() { + for (target_shift, source) in self.index_map.iter().enumerate().rev() { + let target = self.first_to_remove + target_shift; + col[*source] = col[target]; + col[target] = 0.0; + } + } + } + + fn with_rearranged_rows_mut<C: na::Dim, S: StorageMut<Real, Dyn, C>>( + &self, + mat: &mut na::Matrix<Real, Dyn, C, S>, + mut f: impl FnMut(&mut na::MatrixViewMut<Real, Dyn, C, S::RStride, S::CStride>), + ) { + self.rearrange_rows(mat, true); + let effective_dim = self.dim_after_removal(mat.nrows()); + if effective_dim > 0 { + f(&mut mat.rows_mut(0, effective_dim)); + } + self.inv_rearrange_rows(mat); + } +} + +#[cfg(test)] +mod test { + use super::IndexSequence; + use crate::math::Real; + use na::{DVector, RowDVector}; + + fn test_sequence() -> IndexSequence { + let mut seq = IndexSequence::new(); + seq.remove(2); + seq.remove(3); + seq.remove(4); + seq.keep(5); + seq.keep(6); + seq.remove(7); + seq.keep(8); + seq + } + + #[test] + fn index_sequence_rearrange_columns() { + let mut seq = test_sequence(); + let mut vec = RowDVector::from_fn(10, |_, c| c as Real); + seq.rearrange_columns(&mut vec, true); + assert_eq!( + vec, + RowDVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + ); + } + + #[test] + fn index_sequence_rearrange_rows() { + let mut seq = test_sequence(); + let mut vec = DVector::from_fn(10, |r, _| r as Real); + seq.rearrange_rows(&mut vec, true); + assert_eq!( + vec, + DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + ); + seq.inv_rearrange_rows(&mut vec); + assert_eq!( + vec, + DVector::from(vec![0.0, 1.0, 0.0, 0.0, 0.0, 5.0, 6.0, 0.0, 8.0, 0.0]) + ); + } + + #[test] + fn index_sequence_with_rearranged_rows_mut() { + let mut seq = test_sequence(); + let mut vec = DVector::from_fn(10, |r, _| r as Real); + seq.with_rearranged_rows_mut(&mut vec, |v| { + assert_eq!(v.len(), 5); + assert_eq!(*v, DVector::from(vec![0.0, 1.0, 5.0, 6.0, 8.0])); + *v *= 10.0; + }); + assert_eq!( + vec, + DVector::from(vec![0.0, 10.0, 0.0, 0.0, 0.0, 50.0, 60.0, 0.0, 80.0, 0.0]) + ); + } +} diff --git a/src/dynamics/joint/multibody_joint/multibody_joint.rs b/src/dynamics/joint/multibody_joint/multibody_joint.rs index 11ea890..6b37cb9 100644 --- a/src/dynamics/joint/multibody_joint/multibody_joint.rs +++ b/src/dynamics/joint/multibody_joint/multibody_joint.rs @@ -17,28 +17,33 @@ use na::{UnitQuaternion, Vector3}; pub struct MultibodyJoint { /// The joint’s description. pub data: GenericJoint, + pub kinematic: bool, pub(crate) coords: SpacialVector<Real>, pub(crate) joint_rot: Rotation<Real>, } impl MultibodyJoint { /// Creates a new multibody joint from its description. - pub fn new(data: GenericJoint) -> Self { + pub fn new(data: GenericJoint, kinematic: bool) -> Self { Self { data, + kinematic, coords: na::zero(), joint_rot: Rotation::identity(), } } pub(crate) fn free(pos: Isometry<Real>) -> Self { - let mut result = Self::new(GenericJoint::default()); + let mut result = Self::new(GenericJoint::default(), false); result.set_free_pos(pos); result } pub(crate) fn fixed(pos: Isometry<Real>) -> Self { - Self::new(FixedJointBuilder::new().local_frame1(pos).build().into()) + Self::new( + FixedJointBuilder::new().local_frame1(pos).build().into(), + false, + ) } pub(crate) fn set_free_pos(&mut self, pos: Isometry<Real>) { diff --git a/src/dynamics/joint/multibody_joint/multibody_joint_set.rs b/src/dynamics/joint/multibody_joint/multibody_joint_set.rs index a95c10b..092a250 100644 --- a/src/dynamics/joint/multibody_joint/multibody_joint_set.rs +++ b/src/dynamics/joint/multibody_joint/multibody_joint_set.rs @@ -127,7 +127,18 @@ impl MultibodyJointSet { }) } - /// Inserts a new multibody_joint into this set. + /// Inserts a new kinematic multibody joint into this set. + pub fn insert_kinematic( + &mut self, + body1: RigidBodyHandle, + body2: RigidBodyHandle, + data: impl Into<GenericJoint>, + wake_up: bool, + ) -> Option<MultibodyJointHandle> { + self.do_insert(body1, body2, data, true, wake_up) + } + + /// Inserts a new multibody joint into this set. pub fn insert( &mut self, body1: RigidBodyHandle, @@ -135,9 +146,21 @@ impl MultibodyJointSet { data: impl Into<GenericJoint>, wake_up: bool, ) -> Option<MultibodyJointHandle> { - let data = data.into(); + self.do_insert(body1, body2, data, false, wake_up) + } + + /// Inserts a new multibody_joint into this set. + fn do_insert( + &mut self, + body1: RigidBodyHandle, + body2: RigidBodyHandle, + data: impl Into<GenericJoint>, + kinematic: bool, + wake_up: bool, + ) -> Option<MultibodyJointHandle> { + println!("Inserting kinematic: {}", kinematic); let link1 = self.rb2mb.get(body1.0).copied().unwrap_or_else(|| { - let mb_handle = self.multibodies.insert(Multibody::with_root(body1)); + let mb_handle = self.multibodies.insert(Multibody::with_root(body1, true)); MultibodyLinkId { graph_id: self.connectivity_graph.graph.add_node(body1), multibody: MultibodyIndex(mb_handle), @@ -146,7 +169,7 @@ impl MultibodyJointSet { }); let link2 = self.rb2mb.get(body2.0).copied().unwrap_or_else(|| { - let mb_handle = self.multibodies.insert(Multibody::with_root(body2)); + let mb_handle = self.multibodies.insert(Multibody::with_root(body2, true)); MultibodyLinkId { graph_id: self.connectivity_graph.graph.add_node(body2), multibody: MultibodyIndex(mb_handle), @@ -174,7 +197,7 @@ impl MultibodyJointSet { link.id += multibody1.num_links(); } - multibody1.append(mb2, link1.id, MultibodyJoint::new(data)); + multibody1.append(mb2, link1.id, MultibodyJoint::new(data.into(), kinematic)); if wake_up { self.to_wake_up.push(body1); |
