/* * JBox2D - A Java Port of Erin Catto's Box2D * * JBox2D homepage: http://jbox2d.sourceforge.net/ * Box2D homepage: http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ package org.jbox2d.dynamics.joints; import org.jbox2d.common.Mat22; import org.jbox2d.common.MathUtils; import org.jbox2d.common.Settings; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.TimeStep; import org.jbox2d.pooling.TLMat22; import org.jbox2d.pooling.TLVec2; //Updated to rev. 56->108 of b2RevoluteJoint.cpp/.h //Point-to-point constraint //C = p2 - p1 //Cdot = v2 - v1 // = v2 + cross(w2, r2) - v1 - cross(w1, r1) //J = [-I -r1_skew I r2_skew ] //Identity used: //w k % (rx i + ry j) = w * (-ry i + rx j) //Motor constraint //Cdot = w2 - w1 //J = [0 0 -1 0 0 1] //K = invI1 + invI2 public class RevoluteJoint extends Joint { public final Vec2 m_localAnchor1; // relative public final Vec2 m_localAnchor2; public final Vec2 m_pivotForce; public float m_motorForce; public float m_limitForce; public float m_limitPositionImpulse; public final Mat22 m_pivotMass; // effective mass for point-to-point constraint. public float m_motorMass; // effective mass for motor/limit angular constraint. public boolean m_enableMotor; public float m_maxMotorTorque; public float m_motorSpeed; public boolean m_enableLimit; public float m_referenceAngle; public float m_lowerAngle; public float m_upperAngle; public LimitState m_limitState; public RevoluteJoint(final RevoluteJointDef def) { super(def); m_localAnchor1 = def.localAnchor1.clone(); m_localAnchor2 = def.localAnchor2.clone(); m_referenceAngle = def.referenceAngle; m_pivotForce = new Vec2(0.0f, 0.0f); m_motorForce = 0.0f; m_limitForce = 0.0f; m_limitPositionImpulse = 0.0f; m_pivotMass = new Mat22(); m_lowerAngle = def.lowerAngle; m_upperAngle = def.upperAngle; m_maxMotorTorque = def.maxMotorTorque; m_motorSpeed = def.motorSpeed; m_enableLimit = def.enableLimit; m_enableMotor = def.enableMotor; } // djm pooled private static final TLVec2 tlr1 = new TLVec2(); private static final TLVec2 tlr2 = new TLVec2(); private static final TLMat22 tlK1 = new TLMat22(); private static final TLMat22 tlK2 = new TLMat22(); private static final TLMat22 tlK3 = new TLMat22(); @Override public void initVelocityConstraints(final TimeStep step) { final Body b1 = m_body1; final Body b2 = m_body2; final Vec2 r1 = tlr1.get(); final Vec2 r2 = tlr2.get(); final Mat22 K1 = tlK1.get(); final Mat22 K2 = tlK2.get(); final Mat22 K3 = tlK3.get(); // Compute the effective mass matrix. //Vec2 r1 = Mat22.mul(b1.m_xf.R, m_localAnchor1.sub(b1.getMemberLocalCenter())); //Vec2 r2 = Mat22.mul(b2.m_xf.R, m_localAnchor2.sub(b2.getMemberLocalCenter())); r1.set(b1.getMemberLocalCenter()); r2.set(b2.getMemberLocalCenter()); r1.subLocal(m_localAnchor1).negateLocal(); r2.subLocal(m_localAnchor2).negateLocal(); Mat22.mulToOut(b1.m_xf.R, r1, r1); Mat22.mulToOut(b2.m_xf.R, r2, r2); // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y] // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] final float invMass1 = b1.m_invMass, invMass2 = b2.m_invMass; final float invI1 = b1.m_invI, invI2 = b2.m_invI; K1.col1.x = invMass1 + invMass2; K1.col2.x = 0.0f; K1.col1.y = 0.0f; K1.col2.y = invMass1 + invMass2; K2.col1.x = invI1 * r1.y * r1.y; K2.col2.x = -invI1 * r1.x * r1.y; K2.col1.y = -invI1 * r1.x * r1.y; K2.col2.y = invI1 * r1.x * r1.x; K3.col1.x = invI2 * r2.y * r2.y; K3.col2.x = -invI2 * r2.x * r2.y; K3.col1.y = -invI2 * r2.x * r2.y; K3.col2.y = invI2 * r2.x * r2.x; //Mat22 K = K1.addLocal(K2).addLocal(K3); K1.addLocal(K2).addLocal(K3); //m_pivotMass = K1.invert(); K1.invertToOut(m_pivotMass); m_motorMass = 1.0f / (invI1 + invI2); if (m_enableMotor == false) { m_motorForce = 0.0f; } if (m_enableLimit) { final float jointAngle = b2.m_sweep.a - b1.m_sweep.a - m_referenceAngle; if (MathUtils.abs(m_upperAngle - m_lowerAngle) < 2.0f * Settings.angularSlop) { m_limitState = LimitState.EQUAL_LIMITS; } else if (jointAngle <= m_lowerAngle) { if (m_limitState != LimitState.AT_LOWER_LIMIT) { m_limitForce = 0.0f; } m_limitState = LimitState.AT_LOWER_LIMIT; } else if (jointAngle >= m_upperAngle) { if (m_limitState != LimitState.AT_UPPER_LIMIT) { m_limitForce = 0.0f; } m_limitState = LimitState.AT_UPPER_LIMIT; }else { m_limitState = LimitState.INACTIVE_LIMIT; m_limitForce = 0.0f; } } else { m_limitForce = 0.0f; } if (step.warmStarting) { b1.m_linearVelocity.x -= step.dt * invMass1 * m_pivotForce.x; b1.m_linearVelocity.y -= step.dt * invMass1 * m_pivotForce.y; b1.m_angularVelocity -= step.dt * invI1 * (Vec2.cross(r1, m_pivotForce) + m_motorForce + m_limitForce); b2.m_linearVelocity.x += step.dt * invMass2 * m_pivotForce.x; b2.m_linearVelocity.y += step.dt * invMass2 * m_pivotForce.y; b2.m_angularVelocity += step.dt * invI2 * (Vec2.cross(r2, m_pivotForce) + m_motorForce + m_limitForce); } else { m_pivotForce.setZero(); m_motorForce = 0.0f; m_limitForce = 0.0f; } m_limitPositionImpulse = 0.0f; } final Vec2 m_lastWarmStartingPivotForce = new Vec2(0.0f,0.0f); //private float m_lastWarmStartingMotorForce = 0.0f; djm not used //private float m_lastWarmStartingLimitForce = 0.0f; //private boolean m_warmStartingOld = true; // djm pooled, some from above private static final TLVec2 tltemp = new TLVec2(); private static final TLVec2 tlpivotCdot = new TLVec2(); private static final TLVec2 tlpivotForce = new TLVec2(); @Override public void solveVelocityConstraints(final TimeStep step) { final Body b1 = m_body1; final Body b2 = m_body2; final Vec2 temp = tltemp.get(); final Vec2 pivotCdot = tlpivotCdot.get(); final Vec2 pivotForce = tlpivotForce.get(); final Vec2 r1 = tlr1.get(); final Vec2 r2 = tlr2.get(); //Vec2 r1 = Mat22.mul(b1.m_xf.R, m_localAnchor1.sub(b1.getMemberLocalCenter())); //Vec2 r2 = Mat22.mul(b2.m_xf.R, m_localAnchor2.sub(b2.getMemberLocalCenter())); r1.set(b1.getMemberLocalCenter()); r2.set(b2.getMemberLocalCenter()); r1.subLocal(m_localAnchor1).negateLocal(); r2.subLocal(m_localAnchor2).negateLocal(); Mat22.mulToOut(b1.m_xf.R, r1, r1); Mat22.mulToOut(b2.m_xf.R, r2, r2); // Solve point-to-point constraint //Vec2 pivotCdot = b2.m_linearVelocity.add( Vec2.cross(b2.m_angularVelocity, r2).subLocal(b1.m_linearVelocity).subLocal(Vec2.cross(b1.m_angularVelocity, r1))); //Vec2 pivotForce = Mat22.mul(m_pivotMass, pivotCdot).mulLocal(-step.inv_dt); Vec2.crossToOut(b1.m_angularVelocity, r1, temp); Vec2.crossToOut(b2.m_angularVelocity, r2, pivotCdot); pivotCdot.subLocal(b1.m_linearVelocity).subLocal(temp).addLocal(b2.m_linearVelocity); Mat22.mulToOut(m_pivotMass, pivotCdot, pivotForce); pivotForce.mulLocal(-step.inv_dt); //if (!step.warmStarting) m_pivotForce.set(pivotForce); //else m_pivotForce.addLocal(pivotForce); //if (step.warmStarting && (!m_warmStartingOld)) m_pivotForce = m_lastWarmStartingPivotForce; if (step.warmStarting) { m_pivotForce.addLocal(pivotForce); m_lastWarmStartingPivotForce.set(m_pivotForce); } else { m_pivotForce.set(m_lastWarmStartingPivotForce); } //Vec2 P = pivotForce.mul(step.dt); final Vec2 P = pivotForce.mulLocal(step.dt); b1.m_linearVelocity.x -= b1.m_invMass * P.x; b1.m_linearVelocity.y -= b1.m_invMass * P.y; b1.m_angularVelocity -= b1.m_invI * Vec2.cross(r1, P); b2.m_linearVelocity.x += b2.m_invMass * P.x; b2.m_linearVelocity.y += b2.m_invMass * P.y; b2.m_angularVelocity += b2.m_invI * Vec2.cross(r2, P); if (m_enableMotor && m_limitState != LimitState.EQUAL_LIMITS) { final float motorCdot = b2.m_angularVelocity - b1.m_angularVelocity - m_motorSpeed; float motorForce = -step.inv_dt * m_motorMass * motorCdot; final float oldMotorForce = m_motorForce; m_motorForce = MathUtils.clamp(m_motorForce + motorForce, -m_maxMotorTorque, m_maxMotorTorque); motorForce = m_motorForce - oldMotorForce; if (!step.warmStarting) { m_motorForce = oldMotorForce; } final float P2 = step.dt * motorForce; b1.m_angularVelocity -= b1.m_invI * P2; b2.m_angularVelocity += b2.m_invI * P2; } if (m_enableLimit && m_limitState != LimitState.INACTIVE_LIMIT) { final float limitCdot = b2.m_angularVelocity - b1.m_angularVelocity; float limitForce = -step.inv_dt * m_motorMass * limitCdot; if (m_limitState == LimitState.EQUAL_LIMITS) { m_limitForce += limitForce; } else if (m_limitState == LimitState.AT_LOWER_LIMIT) { final float oldLimitForce = m_limitForce; m_limitForce = MathUtils.max(m_limitForce + limitForce, 0.0f); limitForce = m_limitForce - oldLimitForce; } else if (m_limitState == LimitState.AT_UPPER_LIMIT) { final float oldLimitForce = m_limitForce; m_limitForce = MathUtils.min(m_limitForce + limitForce, 0.0f); limitForce = m_limitForce - oldLimitForce; } final float P2 = step.dt * limitForce; b1.m_angularVelocity -= b1.m_invI * P2; b2.m_angularVelocity += b2.m_invI * P2; } } // djm pooled, some from above private static final TLVec2 tlp1 = new TLVec2(); private static final TLVec2 tlp2 = new TLVec2(); private static final TLVec2 tlptpC = new TLVec2(); private static final TLVec2 tlimpulse = new TLVec2(); @Override public boolean solvePositionConstraints() { final Body b1 = m_body1; final Body b2 = m_body2; final Vec2 p1 = tlp1.get(); final Vec2 p2 = tlp2.get(); final Vec2 ptpC = tlptpC.get(); final Vec2 impulse = tlimpulse.get(); final Vec2 r1 = tlr1.get(); final Vec2 r2 = tlr2.get(); final Mat22 K1 = tlK1.get(); final Mat22 K2 = tlK2.get(); final Mat22 K3 = tlK3.get(); float positionError = 0f; // Solve point-to-point position error. //Vec2 r1 = Mat22.mul(b1.m_xf.R, m_localAnchor1.sub(b1.getMemberLocalCenter())); //Vec2 r2 = Mat22.mul(b2.m_xf.R, m_localAnchor2.sub(b2.getMemberLocalCenter())); r1.set(b1.getMemberLocalCenter()); r2.set(b2.getMemberLocalCenter()); r1.subLocal(m_localAnchor1).negateLocal(); r2.subLocal(m_localAnchor2).negateLocal(); Mat22.mulToOut(b1.m_xf.R, r1, r1); Mat22.mulToOut(b2.m_xf.R, r2, r2); p1.set(b1.m_sweep.c); p1.addLocal(r1); p2.set(b2.m_sweep.c); p2.addLocal(r2); ptpC.set(p2); ptpC.subLocal(p1); positionError = ptpC.length(); // Prevent overly large corrections. //public b2Vec2 dpMax(b2_maxLinearCorrection, b2_maxLinearCorrection); //ptpC = b2Clamp(ptpC, -dpMax, dpMax); final float invMass1 = b1.m_invMass, invMass2 = b2.m_invMass; final float invI1 = b1.m_invI, invI2 = b2.m_invI; K1.col1.x = invMass1 + invMass2; K1.col2.x = 0.0f; K1.col1.y = 0.0f; K1.col2.y = invMass1 + invMass2; K2.col1.x = invI1 * r1.y * r1.y; K2.col2.x = -invI1 * r1.x * r1.y; K2.col1.y = -invI1 * r1.x * r1.y; K2.col2.y = invI1 * r1.x * r1.x; K3.col1.x = invI2 * r2.y * r2.y; K3.col2.x = -invI2 * r2.x * r2.y; K3.col1.y = -invI2 * r2.x * r2.y; K3.col2.y = invI2 * r2.x * r2.x; final Mat22 K = K1.addLocal(K2).addLocal(K3); K.solveToOut(ptpC.negateLocal(), impulse); b1.m_sweep.c.x -= b1.m_invMass * impulse.x; b1.m_sweep.c.y -= b1.m_invMass * impulse.y; b1.m_sweep.a -= b1.m_invI * Vec2.cross(r1, impulse); b2.m_sweep.c.x += b2.m_invMass * impulse.x; b2.m_sweep.c.y += b2.m_invMass * impulse.y; b2.m_sweep.a += b2.m_invI * Vec2.cross(r2, impulse); b1.synchronizeTransform(); b2.synchronizeTransform(); // Handle limits. float angularError = 0.0f; if (m_enableLimit && m_limitState != LimitState.INACTIVE_LIMIT) { final float angle = b2.m_sweep.a - b1.m_sweep.a - m_referenceAngle; float limitImpulse = 0.0f; if (m_limitState == LimitState.EQUAL_LIMITS) { // Prevent large angular corrections final float limitC = MathUtils.clamp(angle, -Settings.maxAngularCorrection, Settings.maxAngularCorrection); limitImpulse = -m_motorMass * limitC; angularError = MathUtils.abs(limitC); } else if (m_limitState == LimitState.AT_LOWER_LIMIT) { float limitC = angle - m_lowerAngle; angularError = MathUtils.max(0.0f, -limitC); // Prevent large angular corrections and allow some slop. limitC = MathUtils.clamp(limitC + Settings.angularSlop, -Settings.maxAngularCorrection, 0.0f); limitImpulse = -m_motorMass * limitC; final float oldLimitImpulse = m_limitPositionImpulse; m_limitPositionImpulse = MathUtils.max(m_limitPositionImpulse + limitImpulse, 0.0f); limitImpulse = m_limitPositionImpulse - oldLimitImpulse; } else if (m_limitState == LimitState.AT_UPPER_LIMIT) { float limitC = angle - m_upperAngle; angularError = MathUtils.max(0.0f, limitC); // Prevent large angular corrections and allow some slop. limitC = MathUtils.clamp(limitC - Settings.angularSlop, 0.0f, Settings.maxAngularCorrection); limitImpulse = -m_motorMass * limitC; final float oldLimitImpulse = m_limitPositionImpulse; m_limitPositionImpulse = MathUtils.min(m_limitPositionImpulse + limitImpulse, 0.0f); limitImpulse = m_limitPositionImpulse - oldLimitImpulse; } b1.m_sweep.a -= b1.m_invI * limitImpulse; b2.m_sweep.a += b2.m_invI * limitImpulse; b1.synchronizeTransform(); b2.synchronizeTransform(); } return positionError <= Settings.linearSlop && angularError <= Settings.angularSlop; } @Override public Vec2 getAnchor1() { return m_body1.getWorldLocation(m_localAnchor1); } @Override public Vec2 getAnchor2() { return m_body2.getWorldLocation(m_localAnchor2); } @Override public Vec2 getReactionForce() { return m_pivotForce; } @Override public float getReactionTorque() { return m_limitForce; } public float getJointAngle() { final Body b1 = m_body1; final Body b2 = m_body2; return b2.m_sweep.a - b1.m_sweep.a - m_referenceAngle; } public float getJointSpeed() { final Body b1 = m_body1; final Body b2 = m_body2; return b2.m_angularVelocity - b1.m_angularVelocity; } public boolean isMotorEnabled() { return m_enableMotor; } public void enableMotor(final boolean flag) { m_enableMotor = flag; } public float getMotorTorque() { return m_motorForce; } public void setMotorSpeed(final float speed) { m_motorSpeed = speed; } public void setMaxMotorTorque(final float torque) { m_maxMotorTorque = torque; } public boolean isLimitEnabled() { return m_enableLimit; } public void enableLimit(final boolean flag) { m_enableLimit = flag; } public float getLowerLimit() { return m_lowerAngle; } public float getUpperLimit() { return m_upperAngle; } public void setLimits(final float lower, final float upper) { assert(lower <= upper); m_lowerAngle = lower; m_upperAngle = upper; } }