849 lines
30 KiB
C++
849 lines
30 KiB
C++
/** @file
|
|
* @author Daniel Chappuis
|
|
* @copyright 2010-2016 Daniel Chappuis
|
|
* @license BSD 3 clauses (see license file)
|
|
*/
|
|
|
|
// Libraries
|
|
#include <ephysics/constraint/HingeJoint.hpp>
|
|
#include <ephysics/engine/ConstraintSolver.hpp>
|
|
|
|
using namespace ephysics;
|
|
|
|
// Static variables definition
|
|
const float HingeJoint::BETA = float(0.2);
|
|
|
|
// Constructor
|
|
HingeJoint::HingeJoint(const HingeJointInfo& jointInfo)
|
|
: Joint(jointInfo), m_impulseTranslation(0, 0, 0), m_impulseRotation(0, 0),
|
|
m_impulseLowerLimit(0), m_impulseUpperLimit(0), m_impulseMotor(0),
|
|
m_isLimitEnabled(jointInfo.isLimitEnabled), m_isMotorEnabled(jointInfo.isMotorEnabled),
|
|
m_lowerLimit(jointInfo.minAngleLimit), m_upperLimit(jointInfo.maxAngleLimit),
|
|
m_isLowerLimitViolated(false), m_isUpperLimitViolated(false),
|
|
m_motorSpeed(jointInfo.motorSpeed), m_maxMotorTorque(jointInfo.maxMotorTorque) {
|
|
|
|
assert(m_lowerLimit <= 0 && m_lowerLimit >= -2.0 * PI);
|
|
assert(m_upperLimit >= 0 && m_upperLimit <= 2.0 * PI);
|
|
|
|
// Compute the local-space anchor point for each body
|
|
etk::Transform3D transform1 = m_body1->getTransform();
|
|
etk::Transform3D transform2 = m_body2->getTransform();
|
|
m_localAnchorPointBody1 = transform1.getInverse() * jointInfo.m_anchorPointWorldSpace;
|
|
m_localAnchorPointBody2 = transform2.getInverse() * jointInfo.m_anchorPointWorldSpace;
|
|
|
|
// Compute the local-space hinge axis
|
|
m_hingeLocalAxisBody1 = transform1.getOrientation().getInverse() * jointInfo.rotationAxisWorld;
|
|
m_hingeLocalAxisBody2 = transform2.getOrientation().getInverse() * jointInfo.rotationAxisWorld;
|
|
m_hingeLocalAxisBody1.normalize();
|
|
m_hingeLocalAxisBody2.normalize();
|
|
|
|
// Compute the inverse of the initial orientation difference between the two bodies
|
|
m_initOrientationDifferenceInv = transform2.getOrientation() *
|
|
transform1.getOrientation().getInverse();
|
|
m_initOrientationDifferenceInv.normalize();
|
|
m_initOrientationDifferenceInv.inverse();
|
|
}
|
|
|
|
// Destructor
|
|
HingeJoint::~HingeJoint() {
|
|
|
|
}
|
|
|
|
// Initialize before solving the constraint
|
|
void HingeJoint::initBeforeSolve(const ConstraintSolverData& constraintSolverData) {
|
|
|
|
// Initialize the bodies index in the velocity array
|
|
m_indexBody1 = constraintSolverData.mapBodyToConstrainedVelocityIndex.find(m_body1)->second;
|
|
m_indexBody2 = constraintSolverData.mapBodyToConstrainedVelocityIndex.find(m_body2)->second;
|
|
|
|
// Get the bodies positions and orientations
|
|
const vec3& x1 = m_body1->m_centerOfMassWorld;
|
|
const vec3& x2 = m_body2->m_centerOfMassWorld;
|
|
const etk::Quaternion& orientationBody1 = m_body1->getTransform().getOrientation();
|
|
const etk::Quaternion& orientationBody2 = m_body2->getTransform().getOrientation();
|
|
|
|
// Get the inertia tensor of bodies
|
|
m_i1 = m_body1->getInertiaTensorInverseWorld();
|
|
m_i2 = m_body2->getInertiaTensorInverseWorld();
|
|
|
|
// Compute the vector from body center to the anchor point in world-space
|
|
m_r1World = orientationBody1 * m_localAnchorPointBody1;
|
|
m_r2World = orientationBody2 * m_localAnchorPointBody2;
|
|
|
|
// Compute the current angle around the hinge axis
|
|
float hingeAngle = computeCurrentHingeAngle(orientationBody1, orientationBody2);
|
|
|
|
// Check if the limit constraints are violated or not
|
|
float lowerLimitError = hingeAngle - m_lowerLimit;
|
|
float upperLimitError = m_upperLimit - hingeAngle;
|
|
bool oldIsLowerLimitViolated = m_isLowerLimitViolated;
|
|
m_isLowerLimitViolated = lowerLimitError <= 0;
|
|
if (m_isLowerLimitViolated != oldIsLowerLimitViolated) {
|
|
m_impulseLowerLimit = 0.0;
|
|
}
|
|
bool oldIsUpperLimitViolated = m_isUpperLimitViolated;
|
|
m_isUpperLimitViolated = upperLimitError <= 0;
|
|
if (m_isUpperLimitViolated != oldIsUpperLimitViolated) {
|
|
m_impulseUpperLimit = 0.0;
|
|
}
|
|
|
|
// Compute vectors needed in the Jacobian
|
|
mA1 = orientationBody1 * m_hingeLocalAxisBody1;
|
|
vec3 a2 = orientationBody2 * m_hingeLocalAxisBody2;
|
|
mA1.normalize();
|
|
a2.normalize();
|
|
const vec3 b2 = a2.getOrthoVector();
|
|
const vec3 c2 = a2.cross(b2);
|
|
m_b2CrossA1 = b2.cross(mA1);
|
|
m_c2CrossA1 = c2.cross(mA1);
|
|
|
|
// Compute the corresponding skew-symmetric matrices
|
|
etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(m_r1World);
|
|
etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(m_r2World);
|
|
|
|
// Compute the inverse mass matrix K=JM^-1J^t for the 3 translation constraints (3x3 matrix)
|
|
float inverseMassBodies = m_body1->m_massInverse + m_body2->m_massInverse;
|
|
etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0,
|
|
0, inverseMassBodies, 0,
|
|
0, 0, inverseMassBodies)
|
|
+ skewSymmetricMatrixU1 * m_i1 * skewSymmetricMatrixU1.getTranspose()
|
|
+ skewSymmetricMatrixU2 * m_i2 * skewSymmetricMatrixU2.getTranspose();
|
|
m_inverseMassMatrixTranslation.setZero();
|
|
if (m_body1->getType() == DYNAMIC || m_body2->getType() == DYNAMIC) {
|
|
m_inverseMassMatrixTranslation = massMatrix.getInverse();
|
|
}
|
|
|
|
// Compute the bias "b" of the translation constraints
|
|
m_bTranslation.setZero();
|
|
float biasFactor = (BETA / constraintSolverData.timeStep);
|
|
if (m_positionCorrectionTechnique == BAUMGARTE_JOINTS) {
|
|
m_bTranslation = biasFactor * (x2 + m_r2World - x1 - m_r1World);
|
|
}
|
|
|
|
// Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation constraints (2x2 matrix)
|
|
vec3 I1B2CrossA1 = m_i1 * m_b2CrossA1;
|
|
vec3 I1C2CrossA1 = m_i1 * m_c2CrossA1;
|
|
vec3 I2B2CrossA1 = m_i2 * m_b2CrossA1;
|
|
vec3 I2C2CrossA1 = m_i2 * m_c2CrossA1;
|
|
const float el11 = m_b2CrossA1.dot(I1B2CrossA1) +
|
|
m_b2CrossA1.dot(I2B2CrossA1);
|
|
const float el12 = m_b2CrossA1.dot(I1C2CrossA1) +
|
|
m_b2CrossA1.dot(I2C2CrossA1);
|
|
const float el21 = m_c2CrossA1.dot(I1B2CrossA1) +
|
|
m_c2CrossA1.dot(I2B2CrossA1);
|
|
const float el22 = m_c2CrossA1.dot(I1C2CrossA1) +
|
|
m_c2CrossA1.dot(I2C2CrossA1);
|
|
const etk::Matrix2x2 matrixKRotation(el11, el12, el21, el22);
|
|
m_inverseMassMatrixRotation.setZero();
|
|
if (m_body1->getType() == DYNAMIC || m_body2->getType() == DYNAMIC) {
|
|
m_inverseMassMatrixRotation = matrixKRotation.getInverse();
|
|
}
|
|
|
|
// Compute the bias "b" of the rotation constraints
|
|
m_bRotation.setZero();
|
|
if (m_positionCorrectionTechnique == BAUMGARTE_JOINTS) {
|
|
m_bRotation = biasFactor * vec2(mA1.dot(b2), mA1.dot(c2));
|
|
}
|
|
|
|
// If warm-starting is not enabled
|
|
if (!constraintSolverData.isWarmStartingActive) {
|
|
|
|
// Reset all the accumulated impulses
|
|
m_impulseTranslation.setZero();
|
|
m_impulseRotation.setZero();
|
|
m_impulseLowerLimit = 0.0;
|
|
m_impulseUpperLimit = 0.0;
|
|
m_impulseMotor = 0.0;
|
|
}
|
|
|
|
// If the motor or limits are enabled
|
|
if (m_isMotorEnabled || (m_isLimitEnabled && (m_isLowerLimitViolated || m_isUpperLimitViolated))) {
|
|
|
|
// Compute the inverse of the mass matrix K=JM^-1J^t for the limits and motor (1x1 matrix)
|
|
m_inverseMassMatrixLimitMotor = mA1.dot(m_i1 * mA1) + mA1.dot(m_i2 * mA1);
|
|
m_inverseMassMatrixLimitMotor = (m_inverseMassMatrixLimitMotor > 0.0) ?
|
|
1.0f / m_inverseMassMatrixLimitMotor : 0.0f;
|
|
|
|
if (m_isLimitEnabled) {
|
|
|
|
// Compute the bias "b" of the lower limit constraint
|
|
m_bLowerLimit = 0.0;
|
|
if (m_positionCorrectionTechnique == BAUMGARTE_JOINTS) {
|
|
m_bLowerLimit = biasFactor * lowerLimitError;
|
|
}
|
|
|
|
// Compute the bias "b" of the upper limit constraint
|
|
m_bUpperLimit = 0.0;
|
|
if (m_positionCorrectionTechnique == BAUMGARTE_JOINTS) {
|
|
m_bUpperLimit = biasFactor * upperLimitError;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Warm start the constraint (apply the previous impulse at the beginning of the step)
|
|
void HingeJoint::warmstart(const ConstraintSolverData& constraintSolverData) {
|
|
|
|
// Get the velocities
|
|
vec3& v1 = constraintSolverData.linearVelocities[m_indexBody1];
|
|
vec3& v2 = constraintSolverData.linearVelocities[m_indexBody2];
|
|
vec3& w1 = constraintSolverData.angularVelocities[m_indexBody1];
|
|
vec3& w2 = constraintSolverData.angularVelocities[m_indexBody2];
|
|
|
|
// Get the inverse mass and inverse inertia tensors of the bodies
|
|
const float inverseMassBody1 = m_body1->m_massInverse;
|
|
const float inverseMassBody2 = m_body2->m_massInverse;
|
|
|
|
// Compute the impulse P=J^T * lambda for the 2 rotation constraints
|
|
vec3 rotationImpulse = -m_b2CrossA1 * m_impulseRotation.x() - m_c2CrossA1 * m_impulseRotation.y();
|
|
|
|
// Compute the impulse P=J^T * lambda for the lower and upper limits constraints
|
|
const vec3 limitsImpulse = (m_impulseUpperLimit - m_impulseLowerLimit) * mA1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the motor constraint
|
|
const vec3 motorImpulse = -m_impulseMotor * mA1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the 3 translation constraints of body 1
|
|
vec3 linearImpulseBody1 = -m_impulseTranslation;
|
|
vec3 angularImpulseBody1 = m_impulseTranslation.cross(m_r1World);
|
|
|
|
// Compute the impulse P=J^T * lambda for the 2 rotation constraints of body 1
|
|
angularImpulseBody1 += rotationImpulse;
|
|
|
|
// Compute the impulse P=J^T * lambda for the lower and upper limits constraints of body 1
|
|
angularImpulseBody1 += limitsImpulse;
|
|
|
|
// Compute the impulse P=J^T * lambda for the motor constraint of body 1
|
|
angularImpulseBody1 += motorImpulse;
|
|
|
|
// Apply the impulse to the body 1
|
|
v1 += inverseMassBody1 * linearImpulseBody1;
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the 3 translation constraints of body 2
|
|
vec3 angularImpulseBody2 = -m_impulseTranslation.cross(m_r2World);
|
|
|
|
// Compute the impulse P=J^T * lambda for the 2 rotation constraints of body 2
|
|
angularImpulseBody2 += -rotationImpulse;
|
|
|
|
// Compute the impulse P=J^T * lambda for the lower and upper limits constraints of body 2
|
|
angularImpulseBody2 += -limitsImpulse;
|
|
|
|
// Compute the impulse P=J^T * lambda for the motor constraint of body 2
|
|
angularImpulseBody2 += -motorImpulse;
|
|
|
|
// Apply the impulse to the body 2
|
|
v2 += inverseMassBody2 * m_impulseTranslation;
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
}
|
|
|
|
// Solve the velocity constraint
|
|
void HingeJoint::solveVelocityConstraint(const ConstraintSolverData& constraintSolverData) {
|
|
|
|
// Get the velocities
|
|
vec3& v1 = constraintSolverData.linearVelocities[m_indexBody1];
|
|
vec3& v2 = constraintSolverData.linearVelocities[m_indexBody2];
|
|
vec3& w1 = constraintSolverData.angularVelocities[m_indexBody1];
|
|
vec3& w2 = constraintSolverData.angularVelocities[m_indexBody2];
|
|
|
|
// Get the inverse mass and inverse inertia tensors of the bodies
|
|
float inverseMassBody1 = m_body1->m_massInverse;
|
|
float inverseMassBody2 = m_body2->m_massInverse;
|
|
|
|
// --------------- Translation Constraints --------------- //
|
|
|
|
// Compute J*v
|
|
const vec3 JvTranslation = v2 + w2.cross(m_r2World) - v1 - w1.cross(m_r1World);
|
|
|
|
// Compute the Lagrange multiplier lambda
|
|
const vec3 deltaLambdaTranslation = m_inverseMassMatrixTranslation *
|
|
(-JvTranslation - m_bTranslation);
|
|
m_impulseTranslation += deltaLambdaTranslation;
|
|
|
|
// Compute the impulse P=J^T * lambda of body 1
|
|
const vec3 linearImpulseBody1 = -deltaLambdaTranslation;
|
|
vec3 angularImpulseBody1 = deltaLambdaTranslation.cross(m_r1World);
|
|
|
|
// Apply the impulse to the body 1
|
|
v1 += inverseMassBody1 * linearImpulseBody1;
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda of body 2
|
|
vec3 angularImpulseBody2 = -deltaLambdaTranslation.cross(m_r2World);
|
|
|
|
// Apply the impulse to the body 2
|
|
v2 += inverseMassBody2 * deltaLambdaTranslation;
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
|
|
// --------------- Rotation Constraints --------------- //
|
|
|
|
// Compute J*v for the 2 rotation constraints
|
|
const vec2 JvRotation(-m_b2CrossA1.dot(w1) + m_b2CrossA1.dot(w2),
|
|
-m_c2CrossA1.dot(w1) + m_c2CrossA1.dot(w2));
|
|
|
|
// Compute the Lagrange multiplier lambda for the 2 rotation constraints
|
|
vec2 deltaLambdaRotation = m_inverseMassMatrixRotation * (-JvRotation - m_bRotation);
|
|
m_impulseRotation += deltaLambdaRotation;
|
|
|
|
// Compute the impulse P=J^T * lambda for the 2 rotation constraints of body 1
|
|
angularImpulseBody1 = -m_b2CrossA1 * deltaLambdaRotation.x() -
|
|
m_c2CrossA1 * deltaLambdaRotation.y();
|
|
|
|
// Apply the impulse to the body 1
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the 2 rotation constraints of body 2
|
|
angularImpulseBody2 = m_b2CrossA1 * deltaLambdaRotation.x() +
|
|
m_c2CrossA1 * deltaLambdaRotation.y();
|
|
|
|
// Apply the impulse to the body 2
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
|
|
// --------------- Limits Constraints --------------- //
|
|
|
|
if (m_isLimitEnabled) {
|
|
|
|
// If the lower limit is violated
|
|
if (m_isLowerLimitViolated) {
|
|
|
|
// Compute J*v for the lower limit constraint
|
|
const float JvLowerLimit = (w2 - w1).dot(mA1);
|
|
|
|
// Compute the Lagrange multiplier lambda for the lower limit constraint
|
|
float deltaLambdaLower = m_inverseMassMatrixLimitMotor * (-JvLowerLimit -m_bLowerLimit);
|
|
float lambdaTemp = m_impulseLowerLimit;
|
|
m_impulseLowerLimit = etk::max(m_impulseLowerLimit + deltaLambdaLower, 0.0f);
|
|
deltaLambdaLower = m_impulseLowerLimit - lambdaTemp;
|
|
|
|
// Compute the impulse P=J^T * lambda for the lower limit constraint of body 1
|
|
const vec3 angularImpulseBody1 = -deltaLambdaLower * mA1;
|
|
|
|
// Apply the impulse to the body 1
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the lower limit constraint of body 2
|
|
const vec3 angularImpulseBody2 = deltaLambdaLower * mA1;
|
|
|
|
// Apply the impulse to the body 2
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
}
|
|
|
|
// If the upper limit is violated
|
|
if (m_isUpperLimitViolated) {
|
|
|
|
// Compute J*v for the upper limit constraint
|
|
const float JvUpperLimit = -(w2 - w1).dot(mA1);
|
|
|
|
// Compute the Lagrange multiplier lambda for the upper limit constraint
|
|
float deltaLambdaUpper = m_inverseMassMatrixLimitMotor * (-JvUpperLimit -m_bUpperLimit);
|
|
float lambdaTemp = m_impulseUpperLimit;
|
|
m_impulseUpperLimit = etk::max(m_impulseUpperLimit + deltaLambdaUpper, 0.0f);
|
|
deltaLambdaUpper = m_impulseUpperLimit - lambdaTemp;
|
|
|
|
// Compute the impulse P=J^T * lambda for the upper limit constraint of body 1
|
|
const vec3 angularImpulseBody1 = deltaLambdaUpper * mA1;
|
|
|
|
// Apply the impulse to the body 1
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the upper limit constraint of body 2
|
|
const vec3 angularImpulseBody2 = -deltaLambdaUpper * mA1;
|
|
|
|
// Apply the impulse to the body 2
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
}
|
|
}
|
|
|
|
// --------------- Motor --------------- //
|
|
|
|
// If the motor is enabled
|
|
if (m_isMotorEnabled) {
|
|
|
|
// Compute J*v for the motor
|
|
const float JvMotor = mA1.dot(w1 - w2);
|
|
|
|
// Compute the Lagrange multiplier lambda for the motor
|
|
const float maxMotorImpulse = m_maxMotorTorque * constraintSolverData.timeStep;
|
|
float deltaLambdaMotor = m_inverseMassMatrixLimitMotor * (-JvMotor - m_motorSpeed);
|
|
float lambdaTemp = m_impulseMotor;
|
|
m_impulseMotor = clamp(m_impulseMotor + deltaLambdaMotor, -maxMotorImpulse, maxMotorImpulse);
|
|
deltaLambdaMotor = m_impulseMotor - lambdaTemp;
|
|
|
|
// Compute the impulse P=J^T * lambda for the motor of body 1
|
|
const vec3 angularImpulseBody1 = -deltaLambdaMotor * mA1;
|
|
|
|
// Apply the impulse to the body 1
|
|
w1 += m_i1 * angularImpulseBody1;
|
|
|
|
// Compute the impulse P=J^T * lambda for the motor of body 2
|
|
const vec3 angularImpulseBody2 = deltaLambdaMotor * mA1;
|
|
|
|
// Apply the impulse to the body 2
|
|
w2 += m_i2 * angularImpulseBody2;
|
|
}
|
|
}
|
|
|
|
// Solve the position constraint (for position error correction)
|
|
void HingeJoint::solvePositionConstraint(const ConstraintSolverData& constraintSolverData) {
|
|
|
|
// If the error position correction technique is not the non-linear-gauss-seidel, we do
|
|
// do not execute this method
|
|
if (m_positionCorrectionTechnique != NON_LINEAR_GAUSS_SEIDEL) return;
|
|
|
|
// Get the bodies positions and orientations
|
|
vec3& x1 = constraintSolverData.positions[m_indexBody1];
|
|
vec3& x2 = constraintSolverData.positions[m_indexBody2];
|
|
etk::Quaternion& q1 = constraintSolverData.orientations[m_indexBody1];
|
|
etk::Quaternion& q2 = constraintSolverData.orientations[m_indexBody2];
|
|
|
|
// Get the inverse mass and inverse inertia tensors of the bodies
|
|
float inverseMassBody1 = m_body1->m_massInverse;
|
|
float inverseMassBody2 = m_body2->m_massInverse;
|
|
|
|
// Recompute the inverse inertia tensors
|
|
m_i1 = m_body1->getInertiaTensorInverseWorld();
|
|
m_i2 = m_body2->getInertiaTensorInverseWorld();
|
|
|
|
// Compute the vector from body center to the anchor point in world-space
|
|
m_r1World = q1 * m_localAnchorPointBody1;
|
|
m_r2World = q2 * m_localAnchorPointBody2;
|
|
|
|
// Compute the current angle around the hinge axis
|
|
float hingeAngle = computeCurrentHingeAngle(q1, q2);
|
|
|
|
// Check if the limit constraints are violated or not
|
|
float lowerLimitError = hingeAngle - m_lowerLimit;
|
|
float upperLimitError = m_upperLimit - hingeAngle;
|
|
m_isLowerLimitViolated = lowerLimitError <= 0;
|
|
m_isUpperLimitViolated = upperLimitError <= 0;
|
|
|
|
// Compute vectors needed in the Jacobian
|
|
mA1 = q1 * m_hingeLocalAxisBody1;
|
|
vec3 a2 = q2 * m_hingeLocalAxisBody2;
|
|
mA1.normalize();
|
|
a2.normalize();
|
|
const vec3 b2 = a2.getOrthoVector();
|
|
const vec3 c2 = a2.cross(b2);
|
|
m_b2CrossA1 = b2.cross(mA1);
|
|
m_c2CrossA1 = c2.cross(mA1);
|
|
|
|
// Compute the corresponding skew-symmetric matrices
|
|
etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(m_r1World);
|
|
etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(m_r2World);
|
|
|
|
// --------------- Translation Constraints --------------- //
|
|
|
|
// Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation constraints
|
|
float inverseMassBodies = m_body1->m_massInverse + m_body2->m_massInverse;
|
|
etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0,
|
|
0, inverseMassBodies, 0,
|
|
0, 0, inverseMassBodies)
|
|
+ skewSymmetricMatrixU1 * m_i1 * skewSymmetricMatrixU1.getTranspose()
|
|
+ skewSymmetricMatrixU2 * m_i2 * skewSymmetricMatrixU2.getTranspose();
|
|
m_inverseMassMatrixTranslation.setZero();
|
|
if (m_body1->getType() == DYNAMIC || m_body2->getType() == DYNAMIC) {
|
|
m_inverseMassMatrixTranslation = massMatrix.getInverse();
|
|
}
|
|
|
|
// Compute position error for the 3 translation constraints
|
|
const vec3 errorTranslation = x2 + m_r2World - x1 - m_r1World;
|
|
|
|
// Compute the Lagrange multiplier lambda
|
|
const vec3 lambdaTranslation = m_inverseMassMatrixTranslation * (-errorTranslation);
|
|
|
|
// Compute the impulse of body 1
|
|
vec3 linearImpulseBody1 = -lambdaTranslation;
|
|
vec3 angularImpulseBody1 = lambdaTranslation.cross(m_r1World);
|
|
|
|
// Compute the pseudo velocity of body 1
|
|
const vec3 v1 = inverseMassBody1 * linearImpulseBody1;
|
|
vec3 w1 = m_i1 * angularImpulseBody1;
|
|
|
|
// Update the body position/orientation of body 1
|
|
x1 += v1;
|
|
q1 += etk::Quaternion(0, w1) * q1 * 0.5f;
|
|
q1.normalize();
|
|
|
|
// Compute the impulse of body 2
|
|
vec3 angularImpulseBody2 = -lambdaTranslation.cross(m_r2World);
|
|
|
|
// Compute the pseudo velocity of body 2
|
|
const vec3 v2 = inverseMassBody2 * lambdaTranslation;
|
|
vec3 w2 = m_i2 * angularImpulseBody2;
|
|
|
|
// Update the body position/orientation of body 2
|
|
x2 += v2;
|
|
q2 += etk::Quaternion(0, w2) * q2 * 0.5f;
|
|
q2.normalize();
|
|
|
|
// --------------- Rotation Constraints --------------- //
|
|
|
|
// Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation constraints (2x2 matrix)
|
|
vec3 I1B2CrossA1 = m_i1 * m_b2CrossA1;
|
|
vec3 I1C2CrossA1 = m_i1 * m_c2CrossA1;
|
|
vec3 I2B2CrossA1 = m_i2 * m_b2CrossA1;
|
|
vec3 I2C2CrossA1 = m_i2 * m_c2CrossA1;
|
|
const float el11 = m_b2CrossA1.dot(I1B2CrossA1) +
|
|
m_b2CrossA1.dot(I2B2CrossA1);
|
|
const float el12 = m_b2CrossA1.dot(I1C2CrossA1) +
|
|
m_b2CrossA1.dot(I2C2CrossA1);
|
|
const float el21 = m_c2CrossA1.dot(I1B2CrossA1) +
|
|
m_c2CrossA1.dot(I2B2CrossA1);
|
|
const float el22 = m_c2CrossA1.dot(I1C2CrossA1) +
|
|
m_c2CrossA1.dot(I2C2CrossA1);
|
|
const etk::Matrix2x2 matrixKRotation(el11, el12, el21, el22);
|
|
m_inverseMassMatrixRotation.setZero();
|
|
if (m_body1->getType() == DYNAMIC || m_body2->getType() == DYNAMIC) {
|
|
m_inverseMassMatrixRotation = matrixKRotation.getInverse();
|
|
}
|
|
|
|
// Compute the position error for the 3 rotation constraints
|
|
const vec2 errorRotation = vec2(mA1.dot(b2), mA1.dot(c2));
|
|
|
|
// Compute the Lagrange multiplier lambda for the 3 rotation constraints
|
|
vec2 lambdaRotation = m_inverseMassMatrixRotation * (-errorRotation);
|
|
|
|
// Compute the impulse P=J^T * lambda for the 3 rotation constraints of body 1
|
|
angularImpulseBody1 = -m_b2CrossA1 * lambdaRotation.x() - m_c2CrossA1 * lambdaRotation.y();
|
|
|
|
// Compute the pseudo velocity of body 1
|
|
w1 = m_i1 * angularImpulseBody1;
|
|
|
|
// Update the body position/orientation of body 1
|
|
q1 += etk::Quaternion(0, w1) * q1 * 0.5f;
|
|
q1.normalize();
|
|
|
|
// Compute the impulse of body 2
|
|
angularImpulseBody2 = m_b2CrossA1 * lambdaRotation.x() + m_c2CrossA1 * lambdaRotation.y();
|
|
|
|
// Compute the pseudo velocity of body 2
|
|
w2 = m_i2 * angularImpulseBody2;
|
|
|
|
// Update the body position/orientation of body 2
|
|
q2 += etk::Quaternion(0, w2) * q2 * 0.5f;
|
|
q2.normalize();
|
|
|
|
// --------------- Limits Constraints --------------- //
|
|
|
|
if (m_isLimitEnabled) {
|
|
|
|
if (m_isLowerLimitViolated || m_isUpperLimitViolated) {
|
|
|
|
// Compute the inverse of the mass matrix K=JM^-1J^t for the limits (1x1 matrix)
|
|
m_inverseMassMatrixLimitMotor = mA1.dot(m_i1 * mA1) + mA1.dot(m_i2 * mA1);
|
|
m_inverseMassMatrixLimitMotor = (m_inverseMassMatrixLimitMotor > 0.0) ?
|
|
1.0f / m_inverseMassMatrixLimitMotor : 0.0f;
|
|
}
|
|
|
|
// If the lower limit is violated
|
|
if (m_isLowerLimitViolated) {
|
|
|
|
// Compute the Lagrange multiplier lambda for the lower limit constraint
|
|
float lambdaLowerLimit = m_inverseMassMatrixLimitMotor * (-lowerLimitError );
|
|
|
|
// Compute the impulse P=J^T * lambda of body 1
|
|
const vec3 angularImpulseBody1 = -lambdaLowerLimit * mA1;
|
|
|
|
// Compute the pseudo velocity of body 1
|
|
const vec3 w1 = m_i1 * angularImpulseBody1;
|
|
|
|
// Update the body position/orientation of body 1
|
|
q1 += etk::Quaternion(0, w1) * q1 * 0.5f;
|
|
q1.normalize();
|
|
|
|
// Compute the impulse P=J^T * lambda of body 2
|
|
const vec3 angularImpulseBody2 = lambdaLowerLimit * mA1;
|
|
|
|
// Compute the pseudo velocity of body 2
|
|
const vec3 w2 = m_i2 * angularImpulseBody2;
|
|
|
|
// Update the body position/orientation of body 2
|
|
q2 += etk::Quaternion(0, w2) * q2 * 0.5f;
|
|
q2.normalize();
|
|
}
|
|
|
|
// If the upper limit is violated
|
|
if (m_isUpperLimitViolated) {
|
|
|
|
// Compute the Lagrange multiplier lambda for the upper limit constraint
|
|
float lambdaUpperLimit = m_inverseMassMatrixLimitMotor * (-upperLimitError);
|
|
|
|
// Compute the impulse P=J^T * lambda of body 1
|
|
const vec3 angularImpulseBody1 = lambdaUpperLimit * mA1;
|
|
|
|
// Compute the pseudo velocity of body 1
|
|
const vec3 w1 = m_i1 * angularImpulseBody1;
|
|
|
|
// Update the body position/orientation of body 1
|
|
q1 += etk::Quaternion(0, w1) * q1 * 0.5f;
|
|
q1.normalize();
|
|
|
|
// Compute the impulse P=J^T * lambda of body 2
|
|
const vec3 angularImpulseBody2 = -lambdaUpperLimit * mA1;
|
|
|
|
// Compute the pseudo velocity of body 2
|
|
const vec3 w2 = m_i2 * angularImpulseBody2;
|
|
|
|
// Update the body position/orientation of body 2
|
|
q2 += etk::Quaternion(0, w2) * q2 * 0.5f;
|
|
q2.normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Enable/Disable the limits of the joint
|
|
/**
|
|
* @param isLimitEnabled True if you want to enable the limits of the joint and
|
|
* false otherwise
|
|
*/
|
|
void HingeJoint::enableLimit(bool isLimitEnabled) {
|
|
|
|
if (isLimitEnabled != m_isLimitEnabled) {
|
|
|
|
m_isLimitEnabled = isLimitEnabled;
|
|
|
|
// Reset the limits
|
|
resetLimits();
|
|
}
|
|
}
|
|
|
|
// Enable/Disable the motor of the joint
|
|
/**
|
|
* @param isMotorEnabled True if you want to enable the motor of the joint and
|
|
* false otherwise
|
|
*/
|
|
void HingeJoint::enableMotor(bool isMotorEnabled) {
|
|
|
|
m_isMotorEnabled = isMotorEnabled;
|
|
m_impulseMotor = 0.0;
|
|
|
|
// Wake up the two bodies of the joint
|
|
m_body1->setIsSleeping(false);
|
|
m_body2->setIsSleeping(false);
|
|
}
|
|
|
|
// Set the minimum angle limit
|
|
/**
|
|
* @param lowerLimit The minimum limit angle of the joint (in radian)
|
|
*/
|
|
void HingeJoint::setMinAngleLimit(float lowerLimit) {
|
|
|
|
assert(m_lowerLimit <= 0 && m_lowerLimit >= -2.0 * PI);
|
|
|
|
if (lowerLimit != m_lowerLimit) {
|
|
|
|
m_lowerLimit = lowerLimit;
|
|
|
|
// Reset the limits
|
|
resetLimits();
|
|
}
|
|
}
|
|
|
|
// Set the maximum angle limit
|
|
/**
|
|
* @param upperLimit The maximum limit angle of the joint (in radian)
|
|
*/
|
|
void HingeJoint::setMaxAngleLimit(float upperLimit) {
|
|
|
|
assert(upperLimit >= 0 && upperLimit <= 2.0 * PI);
|
|
|
|
if (upperLimit != m_upperLimit) {
|
|
|
|
m_upperLimit = upperLimit;
|
|
|
|
// Reset the limits
|
|
resetLimits();
|
|
}
|
|
}
|
|
|
|
// Reset the limits
|
|
void HingeJoint::resetLimits() {
|
|
|
|
// Reset the accumulated impulses for the limits
|
|
m_impulseLowerLimit = 0.0;
|
|
m_impulseUpperLimit = 0.0;
|
|
|
|
// Wake up the two bodies of the joint
|
|
m_body1->setIsSleeping(false);
|
|
m_body2->setIsSleeping(false);
|
|
}
|
|
|
|
// Set the motor speed
|
|
void HingeJoint::setMotorSpeed(float motorSpeed) {
|
|
|
|
if (motorSpeed != m_motorSpeed) {
|
|
|
|
m_motorSpeed = motorSpeed;
|
|
|
|
// Wake up the two bodies of the joint
|
|
m_body1->setIsSleeping(false);
|
|
m_body2->setIsSleeping(false);
|
|
}
|
|
}
|
|
|
|
// Set the maximum motor torque
|
|
/**
|
|
* @param maxMotorTorque The maximum torque (in Newtons) of the joint motor
|
|
*/
|
|
void HingeJoint::setMaxMotorTorque(float maxMotorTorque) {
|
|
|
|
if (maxMotorTorque != m_maxMotorTorque) {
|
|
|
|
assert(m_maxMotorTorque >= 0.0);
|
|
m_maxMotorTorque = maxMotorTorque;
|
|
|
|
// Wake up the two bodies of the joint
|
|
m_body1->setIsSleeping(false);
|
|
m_body2->setIsSleeping(false);
|
|
}
|
|
}
|
|
|
|
// Given an angle in radian, this method returns the corresponding angle in the range [-pi; pi]
|
|
float HingeJoint::computeNormalizedAngle(float angle) const {
|
|
|
|
// Convert it int32_to the range [-2*pi; 2*pi]
|
|
angle = fmod(angle, PI_TIMES_2);
|
|
|
|
// Convert it int32_to the range [-pi; pi]
|
|
if (angle < -PI) {
|
|
return angle + PI_TIMES_2;
|
|
}
|
|
else if (angle > PI) {
|
|
return angle - PI_TIMES_2;
|
|
}
|
|
else {
|
|
return angle;
|
|
}
|
|
}
|
|
|
|
// Given an "inputAngle" in the range [-pi, pi], this method returns an
|
|
// angle (modulo 2*pi) in the range [-2*pi; 2*pi] that is closest to one of the
|
|
// two angle limits in arguments.
|
|
float HingeJoint::computeCorrespondingAngleNearLimits(float inputAngle, float lowerLimitAngle,
|
|
float upperLimitAngle) const {
|
|
if (upperLimitAngle <= lowerLimitAngle) {
|
|
return inputAngle;
|
|
}
|
|
else if (inputAngle > upperLimitAngle) {
|
|
float diffToUpperLimit = fabs(computeNormalizedAngle(inputAngle - upperLimitAngle));
|
|
float diffToLowerLimit = fabs(computeNormalizedAngle(inputAngle - lowerLimitAngle));
|
|
return (diffToUpperLimit > diffToLowerLimit) ? (inputAngle - PI_TIMES_2) : inputAngle;
|
|
}
|
|
else if (inputAngle < lowerLimitAngle) {
|
|
float diffToUpperLimit = fabs(computeNormalizedAngle(upperLimitAngle - inputAngle));
|
|
float diffToLowerLimit = fabs(computeNormalizedAngle(lowerLimitAngle - inputAngle));
|
|
return (diffToUpperLimit > diffToLowerLimit) ? inputAngle : (inputAngle + PI_TIMES_2);
|
|
}
|
|
else {
|
|
return inputAngle;
|
|
}
|
|
}
|
|
|
|
// Compute the current angle around the hinge axis
|
|
float HingeJoint::computeCurrentHingeAngle(const etk::Quaternion& orientationBody1,
|
|
const etk::Quaternion& orientationBody2) {
|
|
|
|
float hingeAngle;
|
|
|
|
// Compute the current orientation difference between the two bodies
|
|
etk::Quaternion currentOrientationDiff = orientationBody2 * orientationBody1.getInverse();
|
|
currentOrientationDiff.normalize();
|
|
|
|
// Compute the relative rotation considering the initial orientation difference
|
|
etk::Quaternion relativeRotation = currentOrientationDiff * m_initOrientationDifferenceInv;
|
|
relativeRotation.normalize();
|
|
|
|
// A quaternion q = [cos(theta/2); sin(theta/2) * rotAxis] where rotAxis is a unit
|
|
// length vector. We can extract cos(theta/2) with q.w and we can extract |sin(theta/2)| with :
|
|
// |sin(theta/2)| = q.getVectorV().length() since rotAxis is unit length. Note that any
|
|
// rotation can be represented by a quaternion q and -q. Therefore, if the relative rotation
|
|
// axis is not pointing in the same direction as the hinge axis, we use the rotation -q which
|
|
// has the same |sin(theta/2)| value but the value cos(theta/2) is sign inverted. Some details
|
|
// about this trick is explained in the source code of OpenTissue (http://www.opentissue.org).
|
|
float cosHalfAngle = relativeRotation.w();
|
|
float sinHalfAngleAbs = relativeRotation.getVectorV().length();
|
|
|
|
// Compute the dot product of the relative rotation axis and the hinge axis
|
|
float dotProduct = relativeRotation.getVectorV().dot(mA1);
|
|
|
|
// If the relative rotation axis and the hinge axis are pointing the same direction
|
|
if (dotProduct >= 0.0f) {
|
|
hingeAngle = float(2.0) * etk::atan2(sinHalfAngleAbs, cosHalfAngle);
|
|
}
|
|
else {
|
|
hingeAngle = float(2.0) * etk::atan2(sinHalfAngleAbs, -cosHalfAngle);
|
|
}
|
|
|
|
// Convert the angle from range [-2*pi; 2*pi] int32_to the range [-pi; pi]
|
|
hingeAngle = computeNormalizedAngle(hingeAngle);
|
|
|
|
// Compute and return the corresponding angle near one the two limits
|
|
return computeCorrespondingAngleNearLimits(hingeAngle, m_lowerLimit, m_upperLimit);
|
|
}
|
|
|
|
|
|
// Return true if the limits of the joint are enabled
|
|
/**
|
|
* @return True if the limits of the joint are enabled and false otherwise
|
|
*/
|
|
bool HingeJoint::isLimitEnabled() const {
|
|
return m_isLimitEnabled;
|
|
}
|
|
|
|
// Return true if the motor of the joint is enabled
|
|
/**
|
|
* @return True if the motor of joint is enabled and false otherwise
|
|
*/
|
|
bool HingeJoint::isMotorEnabled() const {
|
|
return m_isMotorEnabled;
|
|
}
|
|
|
|
// Return the minimum angle limit
|
|
/**
|
|
* @return The minimum limit angle of the joint (in radian)
|
|
*/
|
|
float HingeJoint::getMinAngleLimit() const {
|
|
return m_lowerLimit;
|
|
}
|
|
|
|
// Return the maximum angle limit
|
|
/**
|
|
* @return The maximum limit angle of the joint (in radian)
|
|
*/
|
|
float HingeJoint::getMaxAngleLimit() const {
|
|
return m_upperLimit;
|
|
}
|
|
|
|
// Return the motor speed
|
|
/**
|
|
* @return The current speed of the joint motor (in radian per second)
|
|
*/
|
|
float HingeJoint::getMotorSpeed() const {
|
|
return m_motorSpeed;
|
|
}
|
|
|
|
// Return the maximum motor torque
|
|
/**
|
|
* @return The maximum torque of the joint motor (in Newtons)
|
|
*/
|
|
float HingeJoint::getMaxMotorTorque() const {
|
|
return m_maxMotorTorque;
|
|
}
|
|
|
|
// Return the int32_tensity of the current torque applied for the joint motor
|
|
/**
|
|
* @param timeStep The current time step (in seconds)
|
|
* @return The int32_tensity of the current torque (in Newtons) of the joint motor
|
|
*/
|
|
float HingeJoint::getMotorTorque(float timeStep) const {
|
|
return m_impulseMotor / timeStep;
|
|
}
|
|
|
|
// Return the number of bytes used by the joint
|
|
size_t HingeJoint::getSizeInBytes() const {
|
|
return sizeof(HingeJoint);
|
|
}
|
|
|
|
|