/*
|
* Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz>
|
*
|
* Bullet Continuous Collision Detection and Physics Library
|
* btConeTwistConstraint is Copyright (c) 2007 Starbreeze Studios
|
*
|
* 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.
|
*
|
* Written by: Marcus Hennix
|
*/
|
|
package com.bulletphysics.dynamics.constraintsolver;
|
|
import com.bulletphysics.BulletGlobals;
|
import com.bulletphysics.dynamics.RigidBody;
|
import com.bulletphysics.linearmath.QuaternionUtil;
|
import com.bulletphysics.linearmath.ScalarUtil;
|
import com.bulletphysics.linearmath.Transform;
|
import com.bulletphysics.linearmath.TransformUtil;
|
import cz.advel.stack.Stack;
|
import javax.vecmath.Matrix3f;
|
import javax.vecmath.Quat4f;
|
import javax.vecmath.Vector3f;
|
|
/**
|
* ConeTwistConstraint can be used to simulate ragdoll joints (upper arm, leg etc).
|
*
|
* @author jezek2
|
*/
|
public class ConeTwistConstraint extends TypedConstraint {
|
|
private JacobianEntry[] jac/*[3]*/ = new JacobianEntry[] { new JacobianEntry(), new JacobianEntry(), new JacobianEntry() }; //3 orthogonal linear constraints
|
|
private final Transform rbAFrame = new Transform();
|
private final Transform rbBFrame = new Transform();
|
|
private float limitSoftness;
|
private float biasFactor;
|
private float relaxationFactor;
|
|
private float swingSpan1;
|
private float swingSpan2;
|
private float twistSpan;
|
|
private final Vector3f swingAxis = new Vector3f();
|
private final Vector3f twistAxis = new Vector3f();
|
|
private float kSwing;
|
private float kTwist;
|
|
private float twistLimitSign;
|
private float swingCorrection;
|
private float twistCorrection;
|
|
private float accSwingLimitImpulse;
|
private float accTwistLimitImpulse;
|
|
private boolean angularOnly = false;
|
private boolean solveTwistLimit;
|
private boolean solveSwingLimit;
|
|
public ConeTwistConstraint() {
|
super(TypedConstraintType.CONETWIST_CONSTRAINT_TYPE);
|
}
|
|
public ConeTwistConstraint(RigidBody rbA, RigidBody rbB, Transform rbAFrame, Transform rbBFrame) {
|
super(TypedConstraintType.CONETWIST_CONSTRAINT_TYPE, rbA, rbB);
|
this.rbAFrame.set(rbAFrame);
|
this.rbBFrame.set(rbBFrame);
|
|
swingSpan1 = 1e30f;
|
swingSpan2 = 1e30f;
|
twistSpan = 1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
|
solveTwistLimit = false;
|
solveSwingLimit = false;
|
}
|
|
public ConeTwistConstraint(RigidBody rbA, Transform rbAFrame) {
|
super(TypedConstraintType.CONETWIST_CONSTRAINT_TYPE, rbA);
|
this.rbAFrame.set(rbAFrame);
|
this.rbBFrame.set(this.rbAFrame);
|
|
swingSpan1 = 1e30f;
|
swingSpan2 = 1e30f;
|
twistSpan = 1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
|
solveTwistLimit = false;
|
solveSwingLimit = false;
|
}
|
|
@Override
|
public void buildJacobian() {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
Vector3f tmp1 = Stack.alloc(Vector3f.class);
|
Vector3f tmp2 = Stack.alloc(Vector3f.class);
|
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
|
appliedImpulse = 0f;
|
|
// set bias, sign, clear accumulator
|
swingCorrection = 0f;
|
twistLimitSign = 0f;
|
solveTwistLimit = false;
|
solveSwingLimit = false;
|
accTwistLimitImpulse = 0f;
|
accSwingLimitImpulse = 0f;
|
|
if (!angularOnly) {
|
Vector3f pivotAInW = (Vector3f) Stack.alloc(rbAFrame.origin);
|
rbA.getCenterOfMassTransform(tmpTrans).transform(pivotAInW);
|
|
Vector3f pivotBInW = (Vector3f) Stack.alloc(rbBFrame.origin);
|
rbB.getCenterOfMassTransform(tmpTrans).transform(pivotBInW);
|
|
Vector3f relPos = Stack.alloc(Vector3f.class);
|
relPos.sub(pivotBInW, pivotAInW);
|
|
// TODO: stack
|
Vector3f[] normal/*[3]*/ = new Vector3f[]{Stack.alloc(Vector3f.class), Stack.alloc(Vector3f.class), Stack.alloc(Vector3f.class)};
|
if (relPos.lengthSquared() > BulletGlobals.FLT_EPSILON) {
|
normal[0].normalize(relPos);
|
}
|
else {
|
normal[0].set(1f, 0f, 0f);
|
}
|
|
TransformUtil.planeSpace1(normal[0], normal[1], normal[2]);
|
|
for (int i = 0; i < 3; i++) {
|
Matrix3f mat1 = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class)).basis;
|
mat1.transpose();
|
|
Matrix3f mat2 = rbB.getCenterOfMassTransform(Stack.alloc(Transform.class)).basis;
|
mat2.transpose();
|
|
tmp1.sub(pivotAInW, rbA.getCenterOfMassPosition(tmp));
|
tmp2.sub(pivotBInW, rbB.getCenterOfMassPosition(tmp));
|
|
jac[i].init(
|
mat1,
|
mat2,
|
tmp1,
|
tmp2,
|
normal[i],
|
rbA.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)),
|
rbA.getInvMass(),
|
rbB.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)),
|
rbB.getInvMass());
|
}
|
}
|
|
Vector3f b1Axis1 = Stack.alloc(Vector3f.class), b1Axis2 = Stack.alloc(Vector3f.class), b1Axis3 = Stack.alloc(Vector3f.class);
|
Vector3f b2Axis1 = Stack.alloc(Vector3f.class), b2Axis2 = Stack.alloc(Vector3f.class);
|
|
rbAFrame.basis.getColumn(0, b1Axis1);
|
getRigidBodyA().getCenterOfMassTransform(tmpTrans).basis.transform(b1Axis1);
|
|
rbBFrame.basis.getColumn(0, b2Axis1);
|
getRigidBodyB().getCenterOfMassTransform(tmpTrans).basis.transform(b2Axis1);
|
|
float swing1 = 0f, swing2 = 0f;
|
|
float swx = 0f, swy = 0f;
|
float thresh = 10f;
|
float fact;
|
|
// Get Frame into world space
|
if (swingSpan1 >= 0.05f) {
|
rbAFrame.basis.getColumn(1, b1Axis2);
|
getRigidBodyA().getCenterOfMassTransform(tmpTrans).basis.transform(b1Axis2);
|
// swing1 = ScalarUtil.atan2Fast(b2Axis1.dot(b1Axis2), b2Axis1.dot(b1Axis1));
|
swx = b2Axis1.dot(b1Axis1);
|
swy = b2Axis1.dot(b1Axis2);
|
swing1 = ScalarUtil.atan2Fast(swy, swx);
|
fact = (swy*swy + swx*swx) * thresh * thresh;
|
fact = fact / (fact + 1f);
|
swing1 *= fact;
|
}
|
|
if (swingSpan2 >= 0.05f) {
|
rbAFrame.basis.getColumn(2, b1Axis3);
|
getRigidBodyA().getCenterOfMassTransform(tmpTrans).basis.transform(b1Axis3);
|
// swing2 = ScalarUtil.atan2Fast(b2Axis1.dot(b1Axis3), b2Axis1.dot(b1Axis1));
|
swx = b2Axis1.dot(b1Axis1);
|
swy = b2Axis1.dot(b1Axis3);
|
swing2 = ScalarUtil.atan2Fast(swy, swx);
|
fact = (swy*swy + swx*swx) * thresh * thresh;
|
fact = fact / (fact + 1f);
|
swing2 *= fact;
|
}
|
|
float RMaxAngle1Sq = 1.0f / (swingSpan1 * swingSpan1);
|
float RMaxAngle2Sq = 1.0f / (swingSpan2 * swingSpan2);
|
float EllipseAngle = Math.abs(swing1*swing1) * RMaxAngle1Sq + Math.abs(swing2*swing2) * RMaxAngle2Sq;
|
|
if (EllipseAngle > 1.0f) {
|
swingCorrection = EllipseAngle - 1.0f;
|
solveSwingLimit = true;
|
|
// Calculate necessary axis & factors
|
tmp1.scale(b2Axis1.dot(b1Axis2), b1Axis2);
|
tmp2.scale(b2Axis1.dot(b1Axis3), b1Axis3);
|
tmp.add(tmp1, tmp2);
|
swingAxis.cross(b2Axis1, tmp);
|
swingAxis.normalize();
|
|
float swingAxisSign = (b2Axis1.dot(b1Axis1) >= 0.0f) ? 1.0f : -1.0f;
|
swingAxis.scale(swingAxisSign);
|
|
kSwing = 1f / (getRigidBodyA().computeAngularImpulseDenominator(swingAxis) +
|
getRigidBodyB().computeAngularImpulseDenominator(swingAxis));
|
|
}
|
|
// Twist limits
|
if (twistSpan >= 0f) {
|
//Vector3f b2Axis2 = Stack.alloc(Vector3f.class);
|
rbBFrame.basis.getColumn(1, b2Axis2);
|
getRigidBodyB().getCenterOfMassTransform(tmpTrans).basis.transform(b2Axis2);
|
|
Quat4f rotationArc = QuaternionUtil.shortestArcQuat(b2Axis1, b1Axis1, Stack.alloc(Quat4f.class));
|
Vector3f TwistRef = QuaternionUtil.quatRotate(rotationArc, b2Axis2, Stack.alloc(Vector3f.class));
|
float twist = ScalarUtil.atan2Fast(TwistRef.dot(b1Axis3), TwistRef.dot(b1Axis2));
|
|
float lockedFreeFactor = (twistSpan > 0.05f) ? limitSoftness : 0f;
|
if (twist <= -twistSpan * lockedFreeFactor) {
|
twistCorrection = -(twist + twistSpan);
|
solveTwistLimit = true;
|
|
twistAxis.add(b2Axis1, b1Axis1);
|
twistAxis.scale(0.5f);
|
twistAxis.normalize();
|
twistAxis.scale(-1.0f);
|
|
kTwist = 1f / (getRigidBodyA().computeAngularImpulseDenominator(twistAxis) +
|
getRigidBodyB().computeAngularImpulseDenominator(twistAxis));
|
|
}
|
else if (twist > twistSpan * lockedFreeFactor) {
|
twistCorrection = (twist - twistSpan);
|
solveTwistLimit = true;
|
|
twistAxis.add(b2Axis1, b1Axis1);
|
twistAxis.scale(0.5f);
|
twistAxis.normalize();
|
|
kTwist = 1f / (getRigidBodyA().computeAngularImpulseDenominator(twistAxis) +
|
getRigidBodyB().computeAngularImpulseDenominator(twistAxis));
|
}
|
}
|
}
|
|
@Override
|
public void solveConstraint(float timeStep) {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
Vector3f tmp2 = Stack.alloc(Vector3f.class);
|
|
Vector3f tmpVec = Stack.alloc(Vector3f.class);
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
|
Vector3f pivotAInW = (Vector3f) Stack.alloc(rbAFrame.origin);
|
rbA.getCenterOfMassTransform(tmpTrans).transform(pivotAInW);
|
|
Vector3f pivotBInW = (Vector3f) Stack.alloc(rbBFrame.origin);
|
rbB.getCenterOfMassTransform(tmpTrans).transform(pivotBInW);
|
|
float tau = 0.3f;
|
|
// linear part
|
if (!angularOnly) {
|
Vector3f rel_pos1 = Stack.alloc(Vector3f.class);
|
rel_pos1.sub(pivotAInW, rbA.getCenterOfMassPosition(tmpVec));
|
|
Vector3f rel_pos2 = Stack.alloc(Vector3f.class);
|
rel_pos2.sub(pivotBInW, rbB.getCenterOfMassPosition(tmpVec));
|
|
Vector3f vel1 = rbA.getVelocityInLocalPoint(rel_pos1, Stack.alloc(Vector3f.class));
|
Vector3f vel2 = rbB.getVelocityInLocalPoint(rel_pos2, Stack.alloc(Vector3f.class));
|
Vector3f vel = Stack.alloc(Vector3f.class);
|
vel.sub(vel1, vel2);
|
|
for (int i = 0; i < 3; i++) {
|
Vector3f normal = jac[i].linearJointAxis;
|
float jacDiagABInv = 1f / jac[i].getDiagonal();
|
|
float rel_vel;
|
rel_vel = normal.dot(vel);
|
// positional error (zeroth order error)
|
tmp.sub(pivotAInW, pivotBInW);
|
float depth = -(tmp).dot(normal); // this is the error projected on the normal
|
float impulse = depth * tau / timeStep * jacDiagABInv - rel_vel * jacDiagABInv;
|
appliedImpulse += impulse;
|
Vector3f impulse_vector = Stack.alloc(Vector3f.class);
|
impulse_vector.scale(impulse, normal);
|
|
tmp.sub(pivotAInW, rbA.getCenterOfMassPosition(tmpVec));
|
rbA.applyImpulse(impulse_vector, tmp);
|
|
tmp.negate(impulse_vector);
|
tmp2.sub(pivotBInW, rbB.getCenterOfMassPosition(tmpVec));
|
rbB.applyImpulse(tmp, tmp2);
|
}
|
}
|
|
{
|
// solve angular part
|
Vector3f angVelA = getRigidBodyA().getAngularVelocity(Stack.alloc(Vector3f.class));
|
Vector3f angVelB = getRigidBodyB().getAngularVelocity(Stack.alloc(Vector3f.class));
|
|
// solve swing limit
|
if (solveSwingLimit) {
|
tmp.sub(angVelB, angVelA);
|
float amplitude = ((tmp).dot(swingAxis) * relaxationFactor * relaxationFactor + swingCorrection * (1f / timeStep) * biasFactor);
|
float impulseMag = amplitude * kSwing;
|
|
// Clamp the accumulated impulse
|
float temp = accSwingLimitImpulse;
|
accSwingLimitImpulse = Math.max(accSwingLimitImpulse + impulseMag, 0.0f);
|
impulseMag = accSwingLimitImpulse - temp;
|
|
Vector3f impulse = Stack.alloc(Vector3f.class);
|
impulse.scale(impulseMag, swingAxis);
|
|
rbA.applyTorqueImpulse(impulse);
|
|
tmp.negate(impulse);
|
rbB.applyTorqueImpulse(tmp);
|
}
|
|
// solve twist limit
|
if (solveTwistLimit) {
|
tmp.sub(angVelB, angVelA);
|
float amplitude = ((tmp).dot(twistAxis) * relaxationFactor * relaxationFactor + twistCorrection * (1f / timeStep) * biasFactor);
|
float impulseMag = amplitude * kTwist;
|
|
// Clamp the accumulated impulse
|
float temp = accTwistLimitImpulse;
|
accTwistLimitImpulse = Math.max(accTwistLimitImpulse + impulseMag, 0.0f);
|
impulseMag = accTwistLimitImpulse - temp;
|
|
Vector3f impulse = Stack.alloc(Vector3f.class);
|
impulse.scale(impulseMag, twistAxis);
|
|
rbA.applyTorqueImpulse(impulse);
|
|
tmp.negate(impulse);
|
rbB.applyTorqueImpulse(tmp);
|
}
|
}
|
}
|
|
public void updateRHS(float timeStep) {
|
}
|
|
public void setAngularOnly(boolean angularOnly) {
|
this.angularOnly = angularOnly;
|
}
|
|
public void setLimit(float _swingSpan1, float _swingSpan2, float _twistSpan) {
|
setLimit(_swingSpan1, _swingSpan2, _twistSpan, 0.8f, 0.3f, 1.0f);
|
}
|
|
public void setLimit(float _swingSpan1, float _swingSpan2, float _twistSpan, float _softness, float _biasFactor, float _relaxationFactor) {
|
swingSpan1 = _swingSpan1;
|
swingSpan2 = _swingSpan2;
|
twistSpan = _twistSpan;
|
|
limitSoftness = _softness;
|
biasFactor = _biasFactor;
|
relaxationFactor = _relaxationFactor;
|
}
|
|
public Transform getAFrame(Transform out) {
|
out.set(rbAFrame);
|
return out;
|
}
|
|
public Transform getBFrame(Transform out) {
|
out.set(rbBFrame);
|
return out;
|
}
|
|
public boolean getSolveTwistLimit() {
|
return solveTwistLimit;
|
}
|
|
public boolean getSolveSwingLimit() {
|
return solveTwistLimit;
|
}
|
|
public float getTwistLimitSign() {
|
return twistLimitSign;
|
}
|
|
}
|