use crate::dynamics::solver::DeltaVel; use crate::dynamics::{ GenericJoint, IntegrationParameters, JointGraphEdge, JointIndex, JointParams, RigidBody, }; use crate::math::{AngularInertia, Dim, Isometry, Real, Rotation, SpacialVector, Vector, DIM}; use crate::na::UnitQuaternion; use crate::parry::math::{AngDim, SpatialVector}; use crate::utils::{WAngularInertia, WCross, WCrossMatrix}; #[cfg(feature = "dim3")] use na::{Matrix3, Matrix6, Vector3, Vector6, U3}; #[cfg(feature = "dim2")] use na::{Matrix3, Vector3}; #[derive(Debug)] pub(crate) struct GenericVelocityConstraint { mj_lambda1: usize, mj_lambda2: usize, joint_id: JointIndex, inv_lhs_lin: Matrix3, inv_lhs_ang: Matrix3, im1: Real, im2: Real, ii1: AngularInertia, ii2: AngularInertia, ii1_sqrt: AngularInertia, ii2_sqrt: AngularInertia, r1: Vector, r2: Vector, basis1: Rotation, basis2: Rotation, dependant_set_mask: SpacialVector, vel: GenericConstraintPart, } impl GenericVelocityConstraint { pub fn compute_velocity_error( min_velocity: &SpatialVector, max_velocity: &SpatialVector, r1: &Vector, r2: &Vector, basis1: &Rotation, basis2: &Rotation, rb1: &RigidBody, rb2: &RigidBody, ) -> SpatialVector { let lin_dvel = basis1.inverse_transform_vector(&(-rb1.linvel - rb1.angvel.gcross(*r1))) + basis2.inverse_transform_vector(&(rb2.linvel + rb2.angvel.gcross(*r2))); let ang_dvel = basis1.inverse_transform_vector(&-rb1.angvel) + basis2.inverse_transform_vector(&rb2.angvel); let min_linvel = min_velocity.xyz(); let min_angvel = min_velocity.fixed_rows::(DIM).into_owned(); let max_linvel = max_velocity.xyz(); let max_angvel = max_velocity.fixed_rows::(DIM).into_owned(); let lin_rhs = lin_dvel - lin_dvel.sup(&min_linvel).inf(&max_linvel); let ang_rhs = ang_dvel - ang_dvel.sup(&min_angvel).inf(&max_angvel); #[cfg(feature = "dim2")] return Vector3::new(lin_rhs.x, lin_rhs.y, ang_rhs); #[cfg(feature = "dim3")] return Vector6::new( lin_rhs.x, lin_rhs.y, lin_rhs.z, ang_rhs.x, ang_rhs.y, ang_rhs.z, ); } pub fn compute_lin_position_error( anchor1: &Isometry, anchor2: &Isometry, basis: &Rotation, min_position: &Vector, max_position: &Vector, ) -> Vector { let dpos = anchor2.translation.vector - anchor1.translation.vector; let local_dpos = basis.inverse_transform_vector(&dpos); local_dpos - local_dpos.sup(min_position).inf(max_position) } pub fn compute_ang_position_error( anchor1: &Isometry, anchor2: &Isometry, basis: &Rotation, min_position: &Vector, max_position: &Vector, ) -> Vector { let drot = anchor2.rotation * anchor1.rotation.inverse(); let local_drot_diff = basis.inverse_transform_vector(&drot.scaled_axis()); let error = local_drot_diff - local_drot_diff.sup(min_position).inf(max_position); let error_code = (error[0] == 0.0) as usize + (error[1] == 0.0) as usize + (error[2] == 0.0) as usize; if error_code == 1 { // Align two axes. let constrained_axis = error.iamin(); let axis1 = anchor1 .rotation .to_rotation_matrix() .into_inner() .column(constrained_axis) .into_owned(); let axis2 = anchor2 .rotation .to_rotation_matrix() .into_inner() .column(constrained_axis) .into_owned(); let rot_cross = UnitQuaternion::rotation_between(&axis1, &axis2) .unwrap_or(UnitQuaternion::identity()); let local_drot_diff = basis.inverse_transform_vector(&rot_cross.scaled_axis()); local_drot_diff - local_drot_diff.sup(min_position).inf(max_position) } else { error } } pub fn invert_partial_delassus_matrix( min_impulse: &Vector, max_impulse: &Vector, dependant_set_mask: &mut Vector, mut delassus: na::Matrix3, ) -> na::Matrix3 { // Adjust the Delassus matrix to take force limits into account. // If a DoF has a force limit, then we need to make its // constraint independent from the others because otherwise // the force clamping will cause errors to propagate in the // other constraints. for i in 0..3 { if min_impulse[i] > -Real::MAX || max_impulse[i] < Real::MAX { let diag = delassus[(i, i)]; delassus.column_mut(i).fill(0.0); delassus.row_mut(i).fill(0.0); delassus[(i, i)] = diag; dependant_set_mask[i] = 0.0; } else { dependant_set_mask[i] = 1.0; } } delassus.try_inverse().unwrap() } pub fn compute_position_error( joint: &GenericJoint, anchor1: &Isometry, anchor2: &Isometry, basis: &Rotation, ) -> SpatialVector { let delta_pos = Isometry::from_parts( anchor2.translation * anchor1.translation.inverse(), anchor2.rotation * anchor1.rotation.inverse(), ); let lin_dpos = basis.inverse_transform_vector(&delta_pos.translation.vector); let ang_dpos = basis.inverse_transform_vector(&delta_pos.rotation.scaled_axis()); let dpos = Vector6::new( lin_dpos.x, lin_dpos.y, lin_dpos.z, ang_dpos.x, ang_dpos.y, ang_dpos.z, ); let error = dpos - dpos.sup(&joint.min_position).inf(&joint.max_position); let error_code = (error[3] == 0.0) as usize + (error[4] == 0.0) as usize + (error[5] == 0.0) as usize; match error_code { 1 => { let constrained_axis = error.rows(3, 3).iamin(); let axis1 = anchor1 .rotation .to_rotation_matrix() .into_inner() .column(constrained_axis) .into_owned(); let axis2 = anchor2 .rotation .to_rotation_matrix() .into_inner() .column(constrained_axis) .into_owned(); let rot_cross = UnitQuaternion::rotation_between(&axis1, &axis2) .unwrap_or(UnitQuaternion::identity()); let ang_dpos = basis.inverse_transform_vector(&rot_cross.scaled_axis()); let dpos = Vector6::new( lin_dpos.x, lin_dpos.y, lin_dpos.z, ang_dpos.x, ang_dpos.y, ang_dpos.z, ); dpos - dpos.sup(&joint.min_position).inf(&joint.max_position) } _ => error, } } pub fn from_params( params: &IntegrationParameters, joint_id: JointIndex, rb1: &RigidBody, rb2: &RigidBody, joint: &GenericJoint, ) -> Self { let anchor1 = rb1.position * joint.local_anchor1; let anchor2 = rb2.position * joint.local_anchor2; let basis1 = anchor1.rotation; let basis2 = anchor2.rotation; let im1 = rb1.effective_inv_mass; let im2 = rb2.effective_inv_mass; let ii1 = rb1.effective_world_inv_inertia_sqrt.squared(); let ii2 = rb2.effective_world_inv_inertia_sqrt.squared(); let r1 = anchor1.translation.vector - rb1.world_com.coords; let r2 = anchor2.translation.vector - rb2.world_com.coords; let mut min_impulse = joint.min_impulse; let mut max_impulse = joint.max_impulse; let mut min_pos_impulse = joint.min_pos_impulse; let mut max_pos_impulse = joint.max_pos_impulse; let mut min_velocity = joint.min_velocity; let mut max_velocity = joint.max_velocity; let mut dependant_set_mask = SpacialVector::repeat(1.0); let pos_rhs = Self::compute_position_error(joint, &anchor1, &anchor2, &basis1) * params.inv_dt() * params.joint_erp; for i in 0..6 { if pos_rhs[i] < 0.0 { min_impulse[i] = -Real::MAX; min_pos_impulse[i] = -Real::MAX; min_velocity[i] = 0.0; } if pos_rhs[i] > 0.0 { max_impulse[i] = Real::MAX; max_pos_impulse[i] = Real::MAX; max_velocity[i] = 0.0; } } let rhs = Self::compute_velocity_error( &min_velocity, &max_velocity, &r1, &r2, &basis1, &basis2, rb1, rb2, ); let rhs_lin = rhs.xyz(); let rhs_ang = rhs.fixed_rows::(DIM).into(); // TODO: we should keep the SdpMatrix3 type. let rotmat1 = basis1.to_rotation_matrix().into_inner(); let rotmat2 = basis2.to_rotation_matrix().into_inner(); let rmat1 = r1.gcross_matrix() * rotmat1; let rmat2 = r2.gcross_matrix() * rotmat2; let delassus00 = (ii1.quadform(&rmat1).add_diagonal(im1) + ii2.quadform(&rmat2).add_diagonal(im2)) .into_matrix(); let delassus11 = (ii1.quadform(&rotmat1) + ii2.quadform(&rotmat2)).into_matrix(); let inv_lhs_lin = GenericVelocityConstraint::invert_partial_delassus_matrix( &min_pos_impulse.xyz(), &max_pos_impulse.xyz(), &mut Vector3::zeros(), delassus00, ); let inv_lhs_ang = GenericVelocityConstraint::invert_partial_delassus_matrix( &min_pos_impulse.fixed_rows::(DIM).into_owned(), &max_pos_impulse.fixed_rows::(DIM).into_owned(), &mut Vector3::zeros(), delassus11, ); let impulse = (joint.impulse * params.warmstart_coeff) .inf(&max_impulse) .sup(&min_impulse); let lin_impulse = impulse.xyz(); let ang_impulse = impulse.fixed_rows::(DIM).into_owned(); let min_lin_impulse = min_impulse.xyz(); let min_ang_impulse = min_impulse.fixed_rows::(DIM).into_owned(); let max_lin_impulse = max_impulse.xyz(); let max_ang_impulse = max_impulse.fixed_rows::(DIM).into_owned(); GenericVelocityConstraint { joint_id, mj_lambda1: rb1.active_set_offset, mj_lambda2: rb2.active_set_offset, im1, im2, ii1, ii2, ii1_sqrt: rb1.effective_world_inv_inertia_sqrt, ii2_sqrt: rb2.effective_world_inv_inertia_sqrt, inv_lhs_lin, inv_lhs_ang, r1, r2, basis1, basis2, dependant_set_mask, vel: GenericConstraintPart { lin_impulse, ang_impulse, min_lin_impulse, min_ang_impulse, max_lin_impulse, max_ang_impulse, rhs_lin, rhs_ang, }, } } pub fn warmstart(&self, mj_lambdas: &mut [DeltaVel]) { let mut mj_lambda1 = mj_lambdas[self.mj_lambda1 as usize]; let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize]; let lin_impulse1 = self.basis1 * self.vel.lin_impulse; let ang_impulse1 = self.basis1 * self.vel.ang_impulse; let lin_impulse2 = self.basis2 * self.vel.lin_impulse; let ang_impulse2 = self.basis2 * self.vel.ang_impulse; mj_lambda1.linear += self.im1 * lin_impulse1; mj_lambda1.angular += self .ii1_sqrt .transform_vector(ang_impulse1 + self.r1.gcross(lin_impulse1)); mj_lambda2.linear -= self.im2 * lin_impulse2; mj_lambda2.angular -= self .ii2_sqrt .transform_vector(ang_impulse2 + self.r2.gcross(lin_impulse2)); mj_lambdas[self.mj_lambda1 as usize] = mj_lambda1; mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2; } pub fn solve(&mut self, mj_lambdas: &mut [DeltaVel]) { let mut mj_lambda1 = mj_lambdas[self.mj_lambda1 as usize]; let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize]; let (lin_imp, ang_imp) = self.vel.solve(self, &mut mj_lambda1, &mut mj_lambda2); self.vel.lin_impulse = lin_imp; self.vel.ang_impulse = ang_imp; mj_lambdas[self.mj_lambda1 as usize] = mj_lambda1; mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2; } pub fn writeback_impulses(&self, joints_all: &mut [JointGraphEdge]) { let joint = &mut joints_all[self.joint_id].weight; match &mut joint.params { JointParams::GenericJoint(out) => { out.impulse[0] = self.vel.lin_impulse.x; out.impulse[1] = self.vel.lin_impulse.y; out.impulse[2] = self.vel.lin_impulse.z; out.impulse[3] = self.vel.ang_impulse.x; out.impulse[4] = self.vel.ang_impulse.y; out.impulse[5] = self.vel.ang_impulse.z; } JointParams::RevoluteJoint(out) => { out.impulse[0] = self.vel.lin_impulse.x; out.impulse[1] = self.vel.lin_impulse.y; out.impulse[2] = self.vel.lin_impulse.z; out.motor_impulse = self.vel.ang_impulse.x; out.impulse[3] = self.vel.ang_impulse.y; out.impulse[4] = self.vel.ang_impulse.z; } _ => unimplemented!(), } } } #[derive(Debug)] pub(crate) struct GenericVelocityGroundConstraint { mj_lambda2: usize, joint_id: JointIndex, inv_lhs_lin: Matrix3, inv_lhs_ang: Matrix3, im2: Real, ii2: AngularInertia, ii2_sqrt: AngularInertia, r2: Vector, basis: Rotation, dependant_set_mask: SpacialVector, vel: GenericConstraintPart, } impl GenericVelocityGroundConstraint { pub fn from_params( params: &IntegrationParameters, joint_id: JointIndex, rb1: &RigidBody, rb2: &RigidBody, joint: &GenericJoint, flipped: bool, ) -> Self { let (anchor1, anchor2) = if flipped { ( rb1.position * joint.local_anchor2, rb2.position * joint.local_anchor1, ) } else { ( rb1.position * joint.local_anchor1, rb2.position * joint.local_anchor2, ) }; let basis = anchor2.rotation; let im2 = rb2.effective_inv_mass; let ii2 = rb2.effective_world_inv_inertia_sqrt.squared(); let r1 = anchor1.translation.vector - rb1.world_com.coords; let r2 = anchor2.translation.vector - rb2.world_com.coords; let mut min_impulse = joint.min_impulse; let mut max_impulse = joint.max_impulse; let mut min_pos_impulse = joint.min_pos_impulse; let mut max_pos_impulse = joint.max_pos_impulse; let mut min_velocity = joint.min_velocity; let mut max_velocity = joint.max_velocity; let mut dependant_set_mask = SpacialVector::repeat(1.0); let pos_rhs = GenericVelocityConstraint::compute_position_error(joint, &anchor1, &anchor2, &basis) * params.inv_dt() * params.joint_erp; for i in 0..6 { if pos_rhs[i] < 0.0 { min_impulse[i] = -Real::MAX; min_pos_impulse[i] = -Real::MAX; min_velocity[i] = 0.0; } if pos_rhs[i] > 0.0 { max_impulse[i] = Real::MAX; max_pos_impulse[i] = Real::MAX; max_velocity[i] = 0.0; } } let rhs = GenericVelocityConstraint::compute_velocity_error( &min_velocity, &max_velocity, &r1, &r2, &basis, &basis, rb1, rb2, ); let rhs_lin = rhs.xyz(); let rhs_ang = rhs.fixed_rows::(DIM).into_owned(); // TODO: we should keep the SdpMatrix3 type. let rotmat = basis.to_rotation_matrix().into_inner(); let rmat2 = r2.gcross_matrix() * rotmat; let delassus00 = ii2.quadform(&rmat2).add_diagonal(im2).into_matrix(); let delassus11 = ii2.quadform(&rotmat).into_matrix(); let inv_lhs_lin = GenericVelocityConstraint::invert_partial_delassus_matrix( &min_pos_impulse.xyz(), &max_pos_impulse.xyz(), &mut Vector3::zeros(), delassus00, ); let inv_lhs_ang = GenericVelocityConstraint::invert_partial_delassus_matrix( &min_pos_impulse.fixed_rows::(DIM).into_owned(), &max_pos_impulse.fixed_rows::(DIM).into_owned(), &mut Vector3::zeros(), delassus11, ); let impulse = (joint.impulse * params.warmstart_coeff) .inf(&max_impulse) .sup(&min_impulse); let lin_impulse = impulse.xyz(); let ang_impulse = impulse.fixed_rows::(DIM).into_owned(); let min_lin_impulse = min_impulse.xyz(); let min_ang_impulse = min_impulse.fixed_rows::(DIM).into_owned(); let max_lin_impulse = max_impulse.xyz(); let max_ang_impulse = max_impulse.fixed_rows::(DIM).into_owned(); GenericVelocityGroundConstraint { joint_id, mj_lambda2: rb2.active_set_offset, im2, ii2, ii2_sqrt: rb2.effective_world_inv_inertia_sqrt, inv_lhs_lin, inv_lhs_ang, r2, basis, vel: GenericConstraintPart { lin_impulse, ang_impulse, min_lin_impulse, min_ang_impulse, max_lin_impulse, max_ang_impulse, rhs_lin, rhs_ang, }, dependant_set_mask, } } pub fn warmstart(&self, mj_lambdas: &mut [DeltaVel]) { let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize]; let lin_impulse = self.basis * self.vel.lin_impulse; #[cfg(feature = "dim2")] let ang_impulse = self.basis * self.vel.impulse[2]; #[cfg(feature = "dim3")] let ang_impulse = self.basis * self.vel.ang_impulse; mj_lambda2.linear -= self.im2 * lin_impulse; mj_lambda2.angular -= self .ii2_sqrt .transform_vector(ang_impulse + self.r2.gcross(lin_impulse)); mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2; } pub fn solve(&mut self, mj_lambdas: &mut [DeltaVel]) { let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize]; let (lin_imp, ang_imp) = self.vel.solve_ground(self, &mut mj_lambda2); self.vel.lin_impulse = lin_imp; self.vel.ang_impulse = ang_imp; mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2; } // TODO: duplicated code with the non-ground constraint. pub fn writeback_impulses(&self, joints_all: &mut [JointGraphEdge]) { let joint = &mut joints_all[self.joint_id].weight; match &mut joint.params { JointParams::GenericJoint(out) => { out.impulse[0] = self.vel.lin_impulse.x; out.impulse[1] = self.vel.lin_impulse.y; out.impulse[2] = self.vel.lin_impulse.z; out.impulse[3] = self.vel.ang_impulse.x; out.impulse[4] = self.vel.ang_impulse.y; out.impulse[5] = self.vel.ang_impulse.z; } JointParams::RevoluteJoint(out) => { out.impulse[0] = self.vel.lin_impulse.x; out.impulse[1] = self.vel.lin_impulse.y; out.impulse[2] = self.vel.lin_impulse.z; out.motor_impulse = self.vel.ang_impulse.x; out.impulse[3] = self.vel.ang_impulse.y; out.impulse[4] = self.vel.ang_impulse.z; } _ => unimplemented!(), } } } #[derive(Debug)] struct GenericConstraintPart { lin_impulse: Vector3, max_lin_impulse: Vector3, min_lin_impulse: Vector3, rhs_lin: Vector3, ang_impulse: Vector3, max_ang_impulse: Vector3, min_ang_impulse: Vector3, rhs_ang: Vector3, } impl GenericConstraintPart { fn solve( &self, parent: &GenericVelocityConstraint, mj_lambda1: &mut DeltaVel, mj_lambda2: &mut DeltaVel, ) -> (Vector3, Vector3) { let new_lin_impulse; let new_ang_impulse; /* * * Solve translations. * */ { let ang_vel1 = parent.ii1_sqrt.transform_vector(mj_lambda1.angular); let ang_vel2 = parent.ii2_sqrt.transform_vector(mj_lambda2.angular); let dvel = parent .basis1 .inverse_transform_vector(&(-mj_lambda1.linear - ang_vel1.gcross(parent.r1))) + parent .basis2 .inverse_transform_vector(&(mj_lambda2.linear + ang_vel2.gcross(parent.r2))); let err = dvel + self.rhs_lin; new_lin_impulse = (self.lin_impulse + parent.inv_lhs_lin * err) .sup(&self.min_lin_impulse) .inf(&self.max_lin_impulse); let effective_impulse1 = parent.basis1 * (new_lin_impulse - self.lin_impulse); let effective_impulse2 = parent.basis2 * (new_lin_impulse - self.lin_impulse); mj_lambda1.linear += parent.im1 * effective_impulse1; mj_lambda1.angular += parent .ii1_sqrt .transform_vector(parent.r1.gcross(effective_impulse1)); mj_lambda2.linear -= parent.im2 * effective_impulse2; mj_lambda2.angular -= parent .ii2_sqrt .transform_vector(parent.r2.gcross(effective_impulse2)); } /* * * Solve rotations. * */ { let ang_vel1 = parent.ii1_sqrt.transform_vector(mj_lambda1.angular); let ang_vel2 = parent.ii2_sqrt.transform_vector(mj_lambda2.angular); let dvel = parent.basis2.inverse_transform_vector(&ang_vel2) - parent.basis1.inverse_transform_vector(&ang_vel1); let err = dvel + self.rhs_ang; new_ang_impulse = (self.ang_impulse + parent.inv_lhs_ang * err) .sup(&self.min_ang_impulse) .inf(&self.max_ang_impulse); let effective_impulse1 = parent.basis1 * (new_ang_impulse - self.ang_impulse); let effective_impulse2 = parent.basis2 * (new_ang_impulse - self.ang_impulse); mj_lambda1.angular += parent.ii1_sqrt.transform_vector(effective_impulse1); mj_lambda2.angular -= parent.ii2_sqrt.transform_vector(effective_impulse2); } (new_lin_impulse, new_ang_impulse) } fn solve_ground( &self, parent: &GenericVelocityGroundConstraint, mj_lambda2: &mut DeltaVel, ) -> (Vector3, Vector3) { let new_lin_impulse; let new_ang_impulse; /* * * Solve translations. * */ { let ang_vel2 = parent.ii2_sqrt.transform_vector(mj_lambda2.angular); let dvel = parent .basis .inverse_transform_vector(&(mj_lambda2.linear + ang_vel2.gcross(parent.r2))); let err = dvel + self.rhs_lin; new_lin_impulse = (self.lin_impulse + parent.inv_lhs_lin * err) .sup(&self.min_lin_impulse) .inf(&self.max_lin_impulse); let effective_impulse = parent.basis * (new_lin_impulse - self.lin_impulse); mj_lambda2.linear -= parent.im2 * effective_impulse; mj_lambda2.angular -= parent .ii2_sqrt .transform_vector(parent.r2.gcross(effective_impulse)); } /* * * Solve rotations. * */ { let ang_vel2 = parent.ii2_sqrt.transform_vector(mj_lambda2.angular); let dvel = parent.basis.inverse_transform_vector(&ang_vel2); let err = dvel + self.rhs_ang; new_ang_impulse = (self.ang_impulse + parent.inv_lhs_ang * err) .sup(&self.min_ang_impulse) .inf(&self.max_ang_impulse); let effective_impulse = parent.basis * (new_ang_impulse - self.ang_impulse); mj_lambda2.angular -= parent.ii2_sqrt.transform_vector(effective_impulse); } (new_lin_impulse, new_ang_impulse) } }