aboutsummaryrefslogtreecommitdiff
path: root/src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs
diff options
context:
space:
mode:
authorSébastien Crozet <sebcrozet@dimforge.com>2024-01-21 21:02:23 +0100
committerSébastien Crozet <sebcrozet@dimforge.com>2024-01-21 21:02:27 +0100
commit9b87f06a856c4d673642e210f8b0986cfdbac3af (patch)
treeb4f4eaac0e5004f8ba3fccd42e5aea4fd565dcc6 /src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs
parent9ac3503b879f95fcdf5414470ba5aedf195b9a97 (diff)
downloadrapier-9b87f06a856c4d673642e210f8b0986cfdbac3af.tar.gz
rapier-9b87f06a856c4d673642e210f8b0986cfdbac3af.tar.bz2
rapier-9b87f06a856c4d673642e210f8b0986cfdbac3af.zip
feat: implement new "small-steps" solver + joint improvements
Diffstat (limited to 'src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs')
-rw-r--r--src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs1286
1 files changed, 1286 insertions, 0 deletions
diff --git a/src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs b/src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs
new file mode 100644
index 0000000..5523962
--- /dev/null
+++ b/src/dynamics/solver/joint_constraint/joint_generic_constraint_builder.rs
@@ -0,0 +1,1286 @@
+use crate::dynamics::solver::joint_constraint::joint_generic_constraint::{
+ JointGenericOneBodyConstraint, JointGenericTwoBodyConstraint,
+};
+use crate::dynamics::solver::joint_constraint::joint_velocity_constraint::{
+ JointFixedSolverBody, WritebackId,
+};
+use crate::dynamics::solver::joint_constraint::{JointSolverBody, JointTwoBodyConstraintHelper};
+use crate::dynamics::solver::MotorParameters;
+use crate::dynamics::{
+ GenericJoint, ImpulseJoint, IntegrationParameters, JointIndex, Multibody, MultibodyJointSet,
+ MultibodyLinkId, RigidBodySet,
+};
+use crate::math::{Real, Vector, ANG_DIM, DIM, SPATIAL_DIM};
+use crate::utils;
+use crate::utils::IndexMut2;
+use crate::utils::SimdDot;
+use na::{DVector, SVector};
+
+use crate::dynamics::solver::solver_body::SolverBody;
+use crate::dynamics::solver::ConstraintsCounts;
+#[cfg(feature = "dim3")]
+use crate::utils::SimdAngularInertia;
+#[cfg(feature = "dim2")]
+use na::Vector1;
+use parry::math::Isometry;
+
+#[derive(Copy, Clone)]
+enum LinkOrBody {
+ Link(MultibodyLinkId),
+ Body(usize),
+}
+
+#[derive(Copy, Clone)]
+pub struct JointGenericTwoBodyConstraintBuilder {
+ link1: LinkOrBody,
+ link2: LinkOrBody,
+ joint_id: JointIndex,
+ joint: GenericJoint,
+ j_id: usize,
+ // These are solver body for both joints, except that
+ // the world_com is actually in local-space.
+ local_body1: JointSolverBody<Real, 1>,
+ local_body2: JointSolverBody<Real, 1>,
+ multibodies_ndof: usize,
+ constraint_id: usize,
+}
+
+impl JointGenericTwoBodyConstraintBuilder {
+ pub fn invalid() -> Self {
+ Self {
+ link1: LinkOrBody::Body(usize::MAX),
+ link2: LinkOrBody::Body(usize::MAX),
+ joint_id: JointIndex::MAX,
+ joint: GenericJoint::default(),
+ j_id: usize::MAX,
+ local_body1: JointSolverBody::invalid(),
+ local_body2: JointSolverBody::invalid(),
+ multibodies_ndof: usize::MAX,
+ constraint_id: usize::MAX,
+ }
+ }
+
+ pub fn generate(
+ joint_id: JointIndex,
+ joint: &ImpulseJoint,
+ bodies: &RigidBodySet,
+ multibodies: &MultibodyJointSet,
+ out_builder: &mut Self,
+ j_id: &mut usize,
+ jacobians: &mut DVector<Real>,
+ out_constraint_id: &mut usize,
+ ) {
+ let starting_j_id = *j_id;
+ let rb1 = &bodies[joint.body1];
+ let rb2 = &bodies[joint.body2];
+
+ let local_body1 = JointSolverBody {
+ im: rb1.mprops.effective_inv_mass,
+ sqrt_ii: rb1.mprops.effective_world_inv_inertia_sqrt,
+ world_com: rb1.mprops.local_mprops.local_com,
+ solver_vel: [rb1.ids.active_set_offset],
+ };
+ let local_body2 = JointSolverBody {
+ im: rb2.mprops.effective_inv_mass,
+ sqrt_ii: rb2.mprops.effective_world_inv_inertia_sqrt,
+ world_com: rb2.mprops.local_mprops.local_com,
+ solver_vel: [rb2.ids.active_set_offset],
+ };
+
+ let mut multibodies_ndof = 0;
+ let link1 = match multibodies.rigid_body_link(joint.body1) {
+ Some(link) => {
+ multibodies_ndof += multibodies[link.multibody].ndofs();
+ LinkOrBody::Link(*link)
+ }
+ None => {
+ multibodies_ndof += SPATIAL_DIM;
+ LinkOrBody::Body(rb2.ids.active_set_offset)
+ }
+ };
+ let link2 = match multibodies.rigid_body_link(joint.body2) {
+ Some(link) => {
+ multibodies_ndof += multibodies[link.multibody].ndofs();
+ LinkOrBody::Link(*link)
+ }
+ None => {
+ multibodies_ndof += SPATIAL_DIM;
+ LinkOrBody::Body(rb2.ids.active_set_offset)
+ }
+ };
+
+ if multibodies_ndof == 0 {
+ // Both multibodies are fixed, don’t generate any constraint.
+ out_builder.multibodies_ndof = multibodies_ndof;
+ return;
+ }
+
+ // For each solver contact we generate up to SPATIAL_DIM constraints, and each
+ // constraints appends the multibodies jacobian and weighted jacobians.
+ // Also note that for impulse_joints, the rigid-bodies will also add their jacobians
+ // to the generic DVector.
+ // TODO: is this count correct when we take both motors and limits into account?
+ let required_jacobian_len = *j_id + multibodies_ndof * 2 * SPATIAL_DIM;
+
+ // TODO: use a more precise increment.
+ *j_id += multibodies_ndof * 2 * SPATIAL_DIM;
+
+ if jacobians.nrows() < required_jacobian_len && !cfg!(feature = "parallel") {
+ jacobians.resize_vertically_mut(required_jacobian_len, 0.0);
+ }
+
+ *out_builder = Self {
+ link1,
+ link2,
+ joint_id,
+ joint: joint.data,
+ j_id: starting_j_id,
+ local_body1,
+ local_body2,
+ multibodies_ndof,
+ constraint_id: *out_constraint_id,
+ };
+
+ *out_constraint_id += ConstraintsCounts::from_joint(joint).num_constraints;
+ }
+
+ pub fn update(
+ &self,
+ params: &IntegrationParameters,
+ multibodies: &MultibodyJointSet,
+ bodies: &[SolverBody],
+ jacobians: &mut DVector<Real>,
+ out: &mut [JointGenericTwoBodyConstraint],
+ ) {
+ if self.multibodies_ndof == 0 {
+ // The joint is between two static bodies, no constraint needed.
+ return;
+ }
+
+ // NOTE: right now, the "update", is basically reconstructing all the
+ // constraints. Could we make this more incremental?
+ let pos1;
+ let pos2;
+ let mb1;
+ let mb2;
+
+ match self.link1 {
+ LinkOrBody::Link(link) => {
+ let mb = &multibodies[link.multibody];
+ pos1 = &mb.link(link.id).unwrap().local_to_world;
+ mb1 = Some((mb, link.id));
+ }
+ LinkOrBody::Body(body1) => {
+ pos1 = &bodies[body1].position;
+ mb1 = None;
+ }
+ };
+ match self.link2 {
+ LinkOrBody::Link(link) => {
+ let mb = &multibodies[link.multibody];
+ pos2 = &mb.link(link.id).unwrap().local_to_world;
+ mb2 = Some((mb, link.id));
+ }
+ LinkOrBody::Body(body2) => {
+ pos2 = &bodies[body2].position;
+ mb2 = None;
+ }
+ };
+
+ let frame1 = pos1 * self.joint.local_frame1;
+ let frame2 = pos2 * self.joint.local_frame2;
+
+ let joint_body1 = JointSolverBody {
+ world_com: pos1 * self.local_body1.world_com, // the world_com was stored in local-space.
+ ..self.local_body1
+ };
+ let joint_body2 = JointSolverBody {
+ world_com: pos2 * self.local_body2.world_com, // the world_com was stored in local-space.
+ ..self.local_body2
+ };
+
+ let mut j_id = self.j_id;
+
+ JointGenericTwoBodyConstraint::lock_axes(
+ params,
+ self.joint_id,
+ &joint_body1,
+ &joint_body2,
+ mb1,
+ mb2,
+ &frame1,
+ &frame2,
+ &self.joint,
+ jacobians,
+ &mut j_id,
+ &mut out[self.constraint_id..],
+ );
+ }
+}
+
+#[derive(Copy, Clone)]
+pub enum JointGenericOneBodyConstraintBuilder {
+ Internal(JointGenericVelocityOneBodyInternalConstraintBuilder),
+ External(JointGenericVelocityOneBodyExternalConstraintBuilder),
+ Empty,
+}
+
+#[derive(Copy, Clone)]
+pub struct JointGenericVelocityOneBodyInternalConstraintBuilder {
+ link: MultibodyLinkId,
+ j_id: usize,
+ constraint_id: usize,
+}
+
+impl JointGenericOneBodyConstraintBuilder {
+ pub fn invalid() -> Self {
+ Self::Empty
+ }
+
+ pub fn update(
+ &self,
+ params: &IntegrationParameters,
+ multibodies: &MultibodyJointSet,
+ bodies: &[SolverBody],
+ jacobians: &mut DVector<Real>,
+ out: &mut [JointGenericOneBodyConstraint],
+ ) {
+ match self {
+ Self::Empty => {}
+ Self::Internal(builder) => builder.update(params, multibodies, jacobians, out),
+ Self::External(builder) => builder.update(params, multibodies, bodies, jacobians, out),
+ }
+ }
+}
+
+impl JointGenericVelocityOneBodyInternalConstraintBuilder {
+ pub fn num_constraints(multibodies: &MultibodyJointSet, link_id: &MultibodyLinkId) -> usize {
+ let multibody = &multibodies[link_id.multibody];
+ let link = multibody.link(link_id.id).unwrap();
+ link.joint().num_velocity_constraints()
+ }
+
+ pub fn generate(
+ multibodies: &MultibodyJointSet,
+ link_id: &MultibodyLinkId,
+ out_builder: &mut JointGenericOneBodyConstraintBuilder,
+ j_id: &mut usize,
+ jacobians: &mut DVector<Real>,
+ out_constraint_id: &mut usize,
+ ) {
+ let multibody = &multibodies[link_id.multibody];
+ let link = multibody.link(link_id.id).unwrap();
+ let num_constraints = link.joint().num_velocity_constraints();
+
+ if num_constraints == 0 {
+ return;
+ }
+
+ *out_builder = JointGenericOneBodyConstraintBuilder::Internal(Self {
+ link: *link_id,
+ j_id: *j_id,
+ constraint_id: *out_constraint_id,
+ });
+
+ *j_id += num_constraints * multibody.ndofs() * 2;
+ if jacobians.nrows() < *j_id {
+ jacobians.resize_vertically_mut(*j_id, 0.0);
+ }
+
+ *out_constraint_id += num_constraints;
+ }
+
+ pub fn update(
+ &self,
+ params: &IntegrationParameters,
+ multibodies: &MultibodyJointSet,
+ jacobians: &mut DVector<Real>,
+ out: &mut [JointGenericOneBodyConstraint],
+ ) {
+ let mb = &multibodies[self.link.multibody];
+ let link = mb.link(self.link.id).unwrap();
+ link.joint().velocity_constraints(
+ params,
+ mb,
+ link,
+ self.j_id,
+ jacobians,
+ &mut out[self.constraint_id..],
+ );
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct JointGenericVelocityOneBodyExternalConstraintBuilder {
+ body1: JointFixedSolverBody<Real>,
+ frame1: Isometry<Real>,
+ link2: MultibodyLinkId,
+ joint_id: JointIndex,
+ joint: GenericJoint,
+ j_id: usize,
+ flipped: bool,
+ constraint_id: usize,
+ // These are solver body for both joints, except that
+ // the world_com is actually in local-space.
+ local_body2: JointSolverBody<Real, 1>,
+}
+
+impl JointGenericVelocityOneBodyExternalConstraintBuilder {
+ pub fn generate(
+ joint_id: JointIndex,
+ joint: &ImpulseJoint,
+ bodies: &RigidBodySet,
+ multibodies: &MultibodyJointSet,
+ out_builder: &mut JointGenericOneBodyConstraintBuilder,
+ j_id: &mut usize,
+ jacobians: &mut DVector<Real>,
+ out_constraint_id: &mut usize,
+ ) {
+ let mut handle1 = joint.body1;
+ let mut handle2 = joint.body2;
+ let flipped = !bodies[handle2].is_dynamic();
+
+ let local_frame1 = if flipped {
+ std::mem::swap(&mut handle1, &mut handle2);
+ joint.data.local_frame2
+ } else {
+ joint.data.local_frame1
+ };
+
+ let rb1 = &bodies[handle1];
+ let rb2 = &bodies[handle2];
+
+ let frame1 = rb1.pos.position * local_frame1;
+
+ let starting_j_id = *j_id;
+
+ let body1 = JointFixedSolverBody {
+ linvel: rb1.vels.linvel,
+ angvel: rb1.vels.angvel,
+ world_com: rb1.mprops.world_com,
+ };
+ let local_body2 = JointSolverBody {
+ im: rb2.mprops.effective_inv_mass,
+ sqrt_ii: rb2.mprops.effective_world_inv_inertia_sqrt,
+ world_com: rb2.mprops.local_mprops.local_com,
+ solver_vel: [rb2.ids.active_set_offset],
+ };
+
+ let link2 = *multibodies.rigid_body_link(handle2).unwrap();
+ let mb2 = &multibodies[link2.multibody];
+ let multibodies_ndof = mb2.ndofs();
+
+ if multibodies_ndof == 0 {
+ // The multibody is fixed, don’t generate any constraint.
+ *out_builder = JointGenericOneBodyConstraintBuilder::Empty;
+ return;
+ }
+
+ // For each solver contact we generate up to SPATIAL_DIM constraints, and each
+ // constraints appends the multibodies jacobian and weighted jacobians.
+ // Also note that for impulse_joints, the rigid-bodies will also add their jacobians
+ // to the generic DVector.
+ // TODO: is this count correct when we take both motors and limits into account?
+ let required_jacobian_len = *j_id + multibodies_ndof * 2 * SPATIAL_DIM;
+ // TODO: use a more precise increment.
+ *j_id += multibodies_ndof * 2 * SPATIAL_DIM;
+
+ if jacobians.nrows() < required_jacobian_len && !cfg!(feature = "parallel") {
+ jacobians.resize_vertically_mut(required_jacobian_len, 0.0);
+ }
+
+ *out_builder = JointGenericOneBodyConstraintBuilder::External(Self {
+ body1,
+ link2,
+ joint_id,
+ joint: joint.data,
+ j_id: starting_j_id,
+ frame1,
+ local_body2,
+ flipped,
+ constraint_id: *out_constraint_id,
+ });
+
+ *out_constraint_id += ConstraintsCounts::from_joint(joint).num_constraints;
+ }
+
+ pub fn update(
+ &self,
+ params: &IntegrationParameters,
+ multibodies: &MultibodyJointSet,
+ _bodies: &[SolverBody],
+ jacobians: &mut DVector<Real>,
+ out: &mut [JointGenericOneBodyConstraint],
+ ) {
+ // NOTE: right now, the "update", is basically reconstructing all the
+ // constraints. Could we make this more incremental?
+ let mb2 = &multibodies[self.link2.multibody];
+ let pos2 = &mb2.link(self.link2.id).unwrap().local_to_world;
+ let frame2 = pos2
+ * if self.flipped {
+ self.joint.local_frame1
+ } else {
+ self.joint.local_frame2
+ };
+
+ let joint_body2 = JointSolverBody {
+ world_com: pos2 * self.local_body2.world_com, // the world_com was stored in local-space.
+ ..self.local_body2
+ };
+
+ let mut j_id = self.j_id;
+
+ JointGenericOneBodyConstraint::lock_axes(
+ params,
+ self.joint_id,
+ &self.body1,
+ &joint_body2,
+ (mb2, self.link2.id),
+ &self.frame1,
+ &frame2,
+ &self.joint,
+ jacobians,
+ &mut j_id,
+ &mut out[self.constraint_id..],
+ );
+ }
+}
+
+impl JointSolverBody<Real, 1> {
+ pub fn fill_jacobians(
+ &self,
+ unit_force: Vector<Real>,
+ unit_torque: SVector<Real, ANG_DIM>,
+ j_id: &mut usize,
+ jacobians: &mut DVector<Real>,
+ ) {
+ let wj_id = *j_id + SPATIAL_DIM;
+ jacobians
+ .fixed_rows_mut::<DIM>(*j_id)
+ .copy_from(&unit_force);
+ jacobians
+ .fixed_rows_mut::<ANG_DIM>(*j_id + DIM)
+ .copy_from(&unit_torque);
+
+ {
+ let mut out_invm_j = jacobians.fixed_rows_mut::<SPATIAL_DIM>(wj_id);
+ out_invm_j
+ .fixed_rows_mut::<DIM>(0)
+ .copy_from(&self.im.component_mul(&unit_force));
+
+ #[cfg(feature = "dim2")]
+ {
+ out_invm_j[DIM] *= self.sqrt_ii;
+ }
+ #[cfg(feature = "dim3")]
+ {
+ out_invm_j.fixed_rows_mut::<ANG_DIM>(DIM).gemv(
+ 1.0,
+ &self.sqrt_ii.into_matrix(),
+ &unit_torque,
+ 0.0,
+ );
+ }
+ }
+
+ *j_id += SPATIAL_DIM * 2;
+ }
+}
+
+impl JointTwoBodyConstraintHelper<Real> {
+ pub fn lock_jacobians_generic(
+ &self,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ writeback_id: WritebackId,
+ lin_jac: Vector<Real>,
+ ang_jac1: SVector<Real, ANG_DIM>,
+ ang_jac2: SVector<Real, ANG_DIM>,
+ ) -> JointGenericTwoBodyConstraint {
+ let is_rigid_body1 = mb1.is_none();
+ let is_rigid_body2 = mb2.is_none();
+
+ let ndofs1 = mb1.map(|(m, _)| m.ndofs()).unwrap_or(SPATIAL_DIM);
+ let ndofs2 = mb2.map(|(m, _)| m.ndofs()).unwrap_or(SPATIAL_DIM);
+
+ let j_id1 = *j_id;
+ if let Some((mb1, link_id1)) = mb1 {
+ mb1.fill_jacobians(link_id1, lin_jac, ang_jac1, j_id, jacobians);
+ } else {
+ body1.fill_jacobians(lin_jac, ang_jac1, j_id, jacobians);
+ };
+
+ let j_id2 = *j_id;
+ if let Some((mb2, link_id2)) = mb2 {
+ mb2.fill_jacobians(link_id2, lin_jac, ang_jac2, j_id, jacobians);
+ } else {
+ body2.fill_jacobians(lin_jac, ang_jac2, j_id, jacobians);
+ };
+
+ if is_rigid_body1 {
+ let ang_j_id1 = j_id1 + DIM;
+ let ang_wj_id1 = j_id1 + DIM + ndofs1;
+ let (mut j, wj) = jacobians.rows_range_pair_mut(
+ ang_j_id1..ang_j_id1 + ANG_DIM,
+ ang_wj_id1..ang_wj_id1 + ANG_DIM,
+ );
+ j.copy_from(&wj);
+ }
+
+ if is_rigid_body2 {
+ let ang_j_id2 = j_id2 + DIM;
+ let ang_wj_id2 = j_id2 + DIM + ndofs2;
+ let (mut j, wj) = jacobians.rows_range_pair_mut(
+ ang_j_id2..ang_j_id2 + ANG_DIM,
+ ang_wj_id2..ang_wj_id2 + ANG_DIM,
+ );
+ j.copy_from(&wj);
+ }
+
+ let rhs_wo_bias = 0.0;
+
+ let solver_vel1 = mb1.map(|m| m.0.solver_id).unwrap_or(body1.solver_vel[0]);
+ let solver_vel2 = mb2.map(|m| m.0.solver_id).unwrap_or(body2.solver_vel[0]);
+
+ JointGenericTwoBodyConstraint {
+ is_rigid_body1,
+ is_rigid_body2,
+ solver_vel1,
+ solver_vel2,
+ ndofs1,
+ j_id1,
+ ndofs2,
+ j_id2,
+ joint_id,
+ impulse: 0.0,
+ impulse_bounds: [-Real::MAX, Real::MAX],
+ inv_lhs: 0.0,
+ rhs: rhs_wo_bias,
+ rhs_wo_bias,
+ cfm_coeff: 0.0,
+ cfm_gain: 0.0,
+ writeback_id,
+ }
+ }
+
+ pub fn lock_linear_generic(
+ &self,
+ params: &IntegrationParameters,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ locked_axis: usize,
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ let lin_jac = self.basis.column(locked_axis).into_owned();
+ let ang_jac1 = self.cmat1_basis.column(locked_axis).into_owned();
+ let ang_jac2 = self.cmat2_basis.column(locked_axis).into_owned();
+
+ let mut c = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ lin_jac,
+ ang_jac1,
+ ang_jac2,
+ );
+
+ let erp_inv_dt = params.joint_erp_inv_dt();
+ let rhs_bias = lin_jac.dot(&self.lin_err) * erp_inv_dt;
+ c.rhs += rhs_bias;
+ c
+ }
+
+ pub fn limit_linear_generic(
+ &self,
+ params: &IntegrationParameters,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ limited_axis: usize,
+ limits: [Real; 2],
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ let lin_jac = self.basis.column(limited_axis).into_owned();
+ let ang_jac1 = self.cmat1_basis.column(limited_axis).into_owned();
+ let ang_jac2 = self.cmat2_basis.column(limited_axis).into_owned();
+
+ let mut constraint = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ lin_jac,
+ ang_jac1,
+ ang_jac2,
+ );
+
+ let dist = self.lin_err.dot(&lin_jac);
+ let min_enabled = dist <= limits[0];
+ let max_enabled = limits[1] <= dist;
+
+ let erp_inv_dt = params.joint_erp_inv_dt();
+ let rhs_bias = ((dist - limits[1]).max(0.0) - (limits[0] - dist).max(0.0)) * erp_inv_dt;
+ constraint.rhs += rhs_bias;
+ constraint.impulse_bounds = [
+ min_enabled as u32 as Real * -Real::MAX,
+ max_enabled as u32 as Real * Real::MAX,
+ ];
+
+ constraint
+ }
+
+ pub fn motor_linear_generic(
+ &self,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ motor_axis: usize,
+ motor_params: &MotorParameters<Real>,
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ let lin_jac = self.basis.column(motor_axis).into_owned();
+ let ang_jac1 = self.cmat1_basis.column(motor_axis).into_owned();
+ let ang_jac2 = self.cmat2_basis.column(motor_axis).into_owned();
+
+ // TODO: do we need the same trick as for the non-generic constraint?
+ // if locked_ang_axes & (1 << motor_axis) != 0 {
+ // // FIXME: check that this also works for cases
+ // // whene not all the angular axes are locked.
+ // constraint.ang_jac1.fill(0.0);
+ // constraint.ang_jac2.fill(0.0);
+ // }
+
+ let mut constraint = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ lin_jac,
+ ang_jac1,
+ ang_jac2,
+ );
+
+ let mut rhs_wo_bias = 0.0;
+ if motor_params.erp_inv_dt != 0.0 {
+ let dist = self.lin_err.dot(&lin_jac);
+ rhs_wo_bias += (dist - motor_params.target_pos) * motor_params.erp_inv_dt;
+ }
+
+ rhs_wo_bias += -motor_params.target_vel;
+
+ constraint.impulse_bounds = [-motor_params.max_impulse, motor_params.max_impulse];
+ constraint.rhs = rhs_wo_bias;
+ constraint.rhs_wo_bias = rhs_wo_bias;
+ constraint.cfm_coeff = motor_params.cfm_coeff;
+ constraint.cfm_gain = motor_params.cfm_gain;
+ constraint
+ }
+
+ pub fn lock_angular_generic(
+ &self,
+ params: &IntegrationParameters,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ _locked_axis: usize,
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ #[cfg(feature = "dim2")]
+ let ang_jac = Vector1::new(1.0);
+ #[cfg(feature = "dim3")]
+ let ang_jac = self.ang_basis.column(_locked_axis).into_owned();
+
+ let mut constraint = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ na::zero(),
+ ang_jac,
+ ang_jac,
+ );
+
+ let erp_inv_dt = params.joint_erp_inv_dt();
+ #[cfg(feature = "dim2")]
+ let rhs_bias = self.ang_err.im * erp_inv_dt;
+ #[cfg(feature = "dim3")]
+ let rhs_bias = self.ang_err.imag()[_locked_axis] * erp_inv_dt;
+ constraint.rhs += rhs_bias;
+ constraint
+ }
+
+ pub fn limit_angular_generic(
+ &self,
+ params: &IntegrationParameters,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ _limited_axis: usize,
+ limits: [Real; 2],
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ #[cfg(feature = "dim2")]
+ let ang_jac = Vector1::new(1.0);
+ #[cfg(feature = "dim3")]
+ let ang_jac = self.ang_basis.column(_limited_axis).into_owned();
+
+ let mut constraint = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ na::zero(),
+ ang_jac,
+ ang_jac,
+ );
+
+ let s_limits = [(limits[0] / 2.0).sin(), (limits[1] / 2.0).sin()];
+ #[cfg(feature = "dim2")]
+ let s_ang = (self.ang_err.angle() / 2.0).sin();
+ #[cfg(feature = "dim3")]
+ let s_ang = self.ang_err.imag()[_limited_axis];
+ let min_enabled = s_ang <= s_limits[0];
+ let max_enabled = s_limits[1] <= s_ang;
+ let impulse_bounds = [
+ min_enabled as u32 as Real * -Real::MAX,
+ max_enabled as u32 as Real * Real::MAX,
+ ];
+
+ let erp_inv_dt = params.joint_erp_inv_dt();
+ let rhs_bias =
+ ((s_ang - s_limits[1]).max(0.0) - (s_limits[0] - s_ang).max(0.0)) * erp_inv_dt;
+
+ constraint.rhs += rhs_bias;
+ constraint.impulse_bounds = impulse_bounds;
+ constraint
+ }
+
+ pub fn motor_angular_generic(
+ &self,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointSolverBody<Real, 1>,
+ body2: &JointSolverBody<Real, 1>,
+ mb1: Option<(&Multibody, usize)>,
+ mb2: Option<(&Multibody, usize)>,
+ _motor_axis: usize,
+ motor_params: &MotorParameters<Real>,
+ writeback_id: WritebackId,
+ ) -> JointGenericTwoBodyConstraint {
+ #[cfg(feature = "dim2")]
+ let ang_jac = na::Vector1::new(1.0);
+ #[cfg(feature = "dim3")]
+ let ang_jac = self.basis.column(_motor_axis).into_owned();
+
+ let mut constraint = self.lock_jacobians_generic(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ body2,
+ mb1,
+ mb2,
+ writeback_id,
+ na::zero(),
+ ang_jac,
+ ang_jac,
+ );
+
+ let mut rhs_wo_bias = 0.0;
+ if motor_params.erp_inv_dt != 0.0 {
+ #[cfg(feature = "dim2")]
+ let s_ang_dist = (self.ang_err.angle() / 2.0).sin();
+ #[cfg(feature = "dim3")]
+ let s_ang_dist = self.ang_err.imag()[_motor_axis];
+ let s_target_ang = (motor_params.target_pos / 2.0).sin();
+ rhs_wo_bias += utils::smallest_abs_diff_between_sin_angles(s_ang_dist, s_target_ang)
+ * motor_params.erp_inv_dt;
+ }
+
+ rhs_wo_bias += -motor_params.target_vel;
+
+ constraint.rhs_wo_bias = rhs_wo_bias;
+ constraint.rhs = rhs_wo_bias;
+ constraint.cfm_coeff = motor_params.cfm_coeff;
+ constraint.cfm_gain = motor_params.cfm_gain;
+ constraint.impulse_bounds = [-motor_params.max_impulse, motor_params.max_impulse];
+ constraint
+ }
+
+ pub fn finalize_generic_constraints(
+ jacobians: &mut DVector<Real>,
+ constraints: &mut [JointGenericTwoBodyConstraint],
+ ) {
+ // TODO: orthogonalization doesn’t seem to give good results for multibodies?
+ const ORTHOGONALIZE: bool = false;
+ let len = constraints.len();
+
+ if len == 0 {
+ return;
+ }
+
+ let ndofs1 = constraints[0].ndofs1;
+ let ndofs2 = constraints[0].ndofs2;
+
+ // Use the modified Gramm-Schmidt orthogonalization.
+ for j in 0..len {
+ let c_j = &mut constraints[j];
+
+ let jac_j1 = jacobians.rows(c_j.j_id1, ndofs1);
+ let jac_j2 = jacobians.rows(c_j.j_id2, ndofs2);
+ let w_jac_j1 = jacobians.rows(c_j.j_id1 + ndofs1, ndofs1);
+ let w_jac_j2 = jacobians.rows(c_j.j_id2 + ndofs2, ndofs2);
+
+ let dot_jj = jac_j1.dot(&w_jac_j1) + jac_j2.dot(&w_jac_j2);
+ let cfm_gain = dot_jj * c_j.cfm_coeff + c_j.cfm_gain;
+ let inv_dot_jj = crate::utils::simd_inv(dot_jj);
+ c_j.inv_lhs = crate::utils::simd_inv(dot_jj + cfm_gain); // Don’t forget to update the inv_lhs.
+ c_j.cfm_gain = cfm_gain;
+
+ if c_j.impulse_bounds != [-Real::MAX, Real::MAX] {
+ // Don't remove constraints with limited forces from the others
+ // because they may not deliver the necessary forces to fulfill
+ // the removed parts of other constraints.
+ continue;
+ }
+
+ if !ORTHOGONALIZE {
+ continue;
+ }
+
+ for i in (j + 1)..len {
+ let (c_i, c_j) = constraints.index_mut_const(i, j);
+
+ let jac_i1 = jacobians.rows(c_i.j_id1, ndofs1);
+ let jac_i2 = jacobians.rows(c_i.j_id2, ndofs2);
+ let w_jac_j1 = jacobians.rows(c_j.j_id1 + ndofs1, ndofs1);
+ let w_jac_j2 = jacobians.rows(c_j.j_id2 + ndofs2, ndofs2);
+
+ let dot_ij = jac_i1.dot(&w_jac_j1) + jac_i2.dot(&w_jac_j2);
+ let coeff = dot_ij * inv_dot_jj;
+
+ let (mut jac_i, jac_j) = jacobians.rows_range_pair_mut(
+ c_i.j_id1..c_i.j_id1 + 2 * (ndofs1 + ndofs2),
+ c_j.j_id1..c_j.j_id1 + 2 * (ndofs1 + ndofs2),
+ );
+
+ jac_i.axpy(-coeff, &jac_j, 1.0);
+
+ c_i.rhs_wo_bias -= c_j.rhs_wo_bias * coeff;
+ c_i.rhs -= c_j.rhs * coeff;
+ }
+ }
+ }
+}
+
+impl JointTwoBodyConstraintHelper<Real> {
+ pub fn lock_jacobians_generic_one_body(
+ &self,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointFixedSolverBody<Real>,
+ (mb2, link_id2): (&Multibody, usize),
+ writeback_id: WritebackId,
+ lin_jac: Vector<Real>,
+ ang_jac1: SVector<Real, ANG_DIM>,
+ ang_jac2: SVector<Real, ANG_DIM>,
+ ) -> JointGenericOneBodyConstraint {
+ let ndofs2 = mb2.ndofs();
+
+ let proj_vel1 = lin_jac.dot(&body1.linvel) + ang_jac1.gdot(body1.angvel);
+ let j_id2 = *j_id;
+ mb2.fill_jacobians(link_id2, lin_jac, ang_jac2, j_id, jacobians);
+ let rhs_wo_bias = -proj_vel1;
+
+ let solver_vel2 = mb2.solver_id;
+
+ JointGenericOneBodyConstraint {
+ solver_vel2,
+ ndofs2,
+ j_id2,
+ joint_id,
+ impulse: 0.0,
+ impulse_bounds: [-Real::MAX, Real::MAX],
+ inv_lhs: 0.0,
+ rhs: rhs_wo_bias,
+ rhs_wo_bias,
+ cfm_coeff: 0.0,
+ cfm_gain: 0.0,
+ writeback_id,
+ }
+ }
+
+ pub fn lock_linear_generic_one_body(
+ &self,
+ params: &IntegrationParameters,
+ jacobians: &mut DVector<Real>,
+ j_id: &mut usize,
+ joint_id: JointIndex,
+ body1: &JointFixedSolverBody<Real>,
+ mb2: (&Multibody, usize),
+ locked_axis: usize,
+ writeback_id: WritebackId,
+ ) -> JointGenericOneBodyConstraint {
+ let lin_jac = self.basis.column(locked_axis).into_owned();
+ let ang_jac1 = self.cmat1_basis.column(locked_axis).into_owned();
+ let ang_jac2 = self.cmat2_basis.column(locked_axis).into_owned();
+
+ let mut c = self.lock_jacobians_generic_one_body(
+ jacobians,
+ j_id,
+ joint_id,
+ body1,
+ mb2,
+ writeback_id,
+ lin_jac,
+ ang_jac1,
+ ang_ja