/*
|
* Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz>
|
*
|
* Bullet Continuous Collision Detection and Physics Library
|
* Copyright (c) 2003-2008 Erwin Coumans http://www.bulletphysics.com/
|
*
|
* 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.
|
*/
|
|
/* Hinge Constraint by Dirk Gregorius. Limits added by Marcus Hennix at Starbreeze Studios */
|
|
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;
|
|
/**
|
* Hinge constraint between two rigid bodies each with a pivot point that descibes
|
* the axis location in local space. Axis defines the orientation of the hinge axis.
|
*
|
* @author jezek2
|
*/
|
public class HingeConstraint extends TypedConstraint {
|
|
private JacobianEntry[] jac/*[3]*/ = new JacobianEntry[] { new JacobianEntry(), new JacobianEntry(), new JacobianEntry() }; // 3 orthogonal linear constraints
|
private JacobianEntry[] jacAng/*[3]*/ = new JacobianEntry[] { new JacobianEntry(), new JacobianEntry(), new JacobianEntry() }; // 2 orthogonal angular constraints+ 1 for limit/motor
|
|
private final Transform rbAFrame = new Transform(); // constraint axii. Assumes z is hinge axis.
|
private final Transform rbBFrame = new Transform();
|
|
private float motorTargetVelocity;
|
private float maxMotorImpulse;
|
|
private float limitSoftness;
|
private float biasFactor;
|
private float relaxationFactor;
|
|
private float lowerLimit;
|
private float upperLimit;
|
|
private float kHinge;
|
|
private float limitSign;
|
private float correction;
|
|
private float accLimitImpulse;
|
|
private boolean angularOnly;
|
private boolean enableAngularMotor;
|
private boolean solveLimit;
|
|
public HingeConstraint() {
|
super(TypedConstraintType.HINGE_CONSTRAINT_TYPE);
|
enableAngularMotor = false;
|
}
|
|
public HingeConstraint(RigidBody rbA, RigidBody rbB, Vector3f pivotInA, Vector3f pivotInB, Vector3f axisInA, Vector3f axisInB) {
|
super(TypedConstraintType.HINGE_CONSTRAINT_TYPE, rbA, rbB);
|
angularOnly = false;
|
enableAngularMotor = false;
|
|
rbAFrame.origin.set(pivotInA);
|
|
// since no frame is given, assume this to be zero angle and just pick rb transform axis
|
Vector3f rbAxisA1 = Stack.alloc(Vector3f.class);
|
Vector3f rbAxisA2 = Stack.alloc(Vector3f.class);
|
|
Transform centerOfMassA = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
centerOfMassA.basis.getColumn(0, rbAxisA1);
|
float projection = axisInA.dot(rbAxisA1);
|
|
if (projection >= 1.0f - BulletGlobals.SIMD_EPSILON) {
|
centerOfMassA.basis.getColumn(2, rbAxisA1);
|
rbAxisA1.negate();
|
centerOfMassA.basis.getColumn(1, rbAxisA2);
|
} else if (projection <= -1.0f + BulletGlobals.SIMD_EPSILON) {
|
centerOfMassA.basis.getColumn(2, rbAxisA1);
|
centerOfMassA.basis.getColumn(1, rbAxisA2);
|
} else {
|
rbAxisA2.cross(axisInA, rbAxisA1);
|
rbAxisA1.cross(rbAxisA2, axisInA);
|
}
|
|
rbAFrame.basis.setRow(0, rbAxisA1.x, rbAxisA2.x, axisInA.x);
|
rbAFrame.basis.setRow(1, rbAxisA1.y, rbAxisA2.y, axisInA.y);
|
rbAFrame.basis.setRow(2, rbAxisA1.z, rbAxisA2.z, axisInA.z);
|
|
Quat4f rotationArc = QuaternionUtil.shortestArcQuat(axisInA, axisInB, Stack.alloc(Quat4f.class));
|
Vector3f rbAxisB1 = QuaternionUtil.quatRotate(rotationArc, rbAxisA1, Stack.alloc(Vector3f.class));
|
Vector3f rbAxisB2 = Stack.alloc(Vector3f.class);
|
rbAxisB2.cross(axisInB, rbAxisB1);
|
|
rbBFrame.origin.set(pivotInB);
|
rbBFrame.basis.setRow(0, rbAxisB1.x, rbAxisB2.x, -axisInB.x);
|
rbBFrame.basis.setRow(1, rbAxisB1.y, rbAxisB2.y, -axisInB.y);
|
rbBFrame.basis.setRow(2, rbAxisB1.z, rbAxisB2.z, -axisInB.z);
|
|
// start with free
|
lowerLimit = 1e30f;
|
upperLimit = -1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
limitSoftness = 0.9f;
|
solveLimit = false;
|
}
|
|
public HingeConstraint(RigidBody rbA, Vector3f pivotInA, Vector3f axisInA) {
|
super(TypedConstraintType.HINGE_CONSTRAINT_TYPE, rbA);
|
angularOnly = false;
|
enableAngularMotor = false;
|
|
// since no frame is given, assume this to be zero angle and just pick rb transform axis
|
// fixed axis in worldspace
|
Vector3f rbAxisA1 = Stack.alloc(Vector3f.class);
|
Transform centerOfMassA = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
centerOfMassA.basis.getColumn(0, rbAxisA1);
|
|
float projection = rbAxisA1.dot(axisInA);
|
if (projection > BulletGlobals.FLT_EPSILON) {
|
rbAxisA1.scale(projection);
|
rbAxisA1.sub(axisInA);
|
}
|
else {
|
centerOfMassA.basis.getColumn(1, rbAxisA1);
|
}
|
|
Vector3f rbAxisA2 = Stack.alloc(Vector3f.class);
|
rbAxisA2.cross(axisInA, rbAxisA1);
|
|
rbAFrame.origin.set(pivotInA);
|
rbAFrame.basis.setRow(0, rbAxisA1.x, rbAxisA2.x, axisInA.x);
|
rbAFrame.basis.setRow(1, rbAxisA1.y, rbAxisA2.y, axisInA.y);
|
rbAFrame.basis.setRow(2, rbAxisA1.z, rbAxisA2.z, axisInA.z);
|
|
Vector3f axisInB = Stack.alloc(Vector3f.class);
|
axisInB.negate(axisInA);
|
centerOfMassA.basis.transform(axisInB);
|
|
Quat4f rotationArc = QuaternionUtil.shortestArcQuat(axisInA, axisInB, Stack.alloc(Quat4f.class));
|
Vector3f rbAxisB1 = QuaternionUtil.quatRotate(rotationArc, rbAxisA1, Stack.alloc(Vector3f.class));
|
Vector3f rbAxisB2 = Stack.alloc(Vector3f.class);
|
rbAxisB2.cross(axisInB, rbAxisB1);
|
|
rbBFrame.origin.set(pivotInA);
|
centerOfMassA.transform(rbBFrame.origin);
|
rbBFrame.basis.setRow(0, rbAxisB1.x, rbAxisB2.x, axisInB.x);
|
rbBFrame.basis.setRow(1, rbAxisB1.y, rbAxisB2.y, axisInB.y);
|
rbBFrame.basis.setRow(2, rbAxisB1.z, rbAxisB2.z, axisInB.z);
|
|
// start with free
|
lowerLimit = 1e30f;
|
upperLimit = -1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
limitSoftness = 0.9f;
|
solveLimit = false;
|
}
|
|
public HingeConstraint(RigidBody rbA, RigidBody rbB, Transform rbAFrame, Transform rbBFrame) {
|
super(TypedConstraintType.HINGE_CONSTRAINT_TYPE, rbA, rbB);
|
this.rbAFrame.set(rbAFrame);
|
this.rbBFrame.set(rbBFrame);
|
angularOnly = false;
|
enableAngularMotor = false;
|
|
// flip axis
|
this.rbBFrame.basis.m02 *= -1f;
|
this.rbBFrame.basis.m12 *= -1f;
|
this.rbBFrame.basis.m22 *= -1f;
|
|
// start with free
|
lowerLimit = 1e30f;
|
upperLimit = -1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
limitSoftness = 0.9f;
|
solveLimit = false;
|
}
|
|
public HingeConstraint(RigidBody rbA, Transform rbAFrame) {
|
super(TypedConstraintType.HINGE_CONSTRAINT_TYPE, rbA);
|
this.rbAFrame.set(rbAFrame);
|
this.rbBFrame.set(rbAFrame);
|
angularOnly = false;
|
enableAngularMotor = false;
|
|
// not providing rigidbody B means implicitly using worldspace for body B
|
|
// flip axis
|
this.rbBFrame.basis.m02 *= -1f;
|
this.rbBFrame.basis.m12 *= -1f;
|
this.rbBFrame.basis.m22 *= -1f;
|
|
this.rbBFrame.origin.set(this.rbAFrame.origin);
|
rbA.getCenterOfMassTransform(Stack.alloc(Transform.class)).transform(this.rbBFrame.origin);
|
|
// start with free
|
lowerLimit = 1e30f;
|
upperLimit = -1e30f;
|
biasFactor = 0.3f;
|
relaxationFactor = 1.0f;
|
limitSoftness = 0.9f;
|
solveLimit = false;
|
}
|
|
@Override
|
public void buildJacobian() {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
Vector3f tmp1 = Stack.alloc(Vector3f.class);
|
Vector3f tmp2 = Stack.alloc(Vector3f.class);
|
Vector3f tmpVec = Stack.alloc(Vector3f.class);
|
Matrix3f mat1 = Stack.alloc(Matrix3f.class);
|
Matrix3f mat2 = Stack.alloc(Matrix3f.class);
|
|
Transform centerOfMassA = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
Transform centerOfMassB = rbB.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
|
appliedImpulse = 0f;
|
|
if (!angularOnly) {
|
Vector3f pivotAInW = (Vector3f) Stack.alloc(rbAFrame.origin);
|
centerOfMassA.transform(pivotAInW);
|
|
Vector3f pivotBInW = (Vector3f) Stack.alloc(rbBFrame.origin);
|
centerOfMassB.transform(pivotBInW);
|
|
Vector3f relPos = Stack.alloc(Vector3f.class);
|
relPos.sub(pivotBInW, pivotAInW);
|
|
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].set(relPos);
|
normal[0].normalize();
|
}
|
else {
|
normal[0].set(1f, 0f, 0f);
|
}
|
|
TransformUtil.planeSpace1(normal[0], normal[1], normal[2]);
|
|
for (int i = 0; i < 3; i++) {
|
mat1.transpose(centerOfMassA.basis);
|
mat2.transpose(centerOfMassB.basis);
|
|
tmp1.sub(pivotAInW, rbA.getCenterOfMassPosition(tmpVec));
|
tmp2.sub(pivotBInW, rbB.getCenterOfMassPosition(tmpVec));
|
|
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());
|
}
|
}
|
|
// calculate two perpendicular jointAxis, orthogonal to hingeAxis
|
// these two jointAxis require equal angular velocities for both bodies
|
|
// this is unused for now, it's a todo
|
Vector3f jointAxis0local = Stack.alloc(Vector3f.class);
|
Vector3f jointAxis1local = Stack.alloc(Vector3f.class);
|
|
rbAFrame.basis.getColumn(2, tmp);
|
TransformUtil.planeSpace1(tmp, jointAxis0local, jointAxis1local);
|
|
// TODO: check this
|
//getRigidBodyA().getCenterOfMassTransform().getBasis() * m_rbAFrame.getBasis().getColumn(2);
|
|
Vector3f jointAxis0 = (Vector3f) Stack.alloc(jointAxis0local);
|
centerOfMassA.basis.transform(jointAxis0);
|
|
Vector3f jointAxis1 = (Vector3f) Stack.alloc(jointAxis1local);
|
centerOfMassA.basis.transform(jointAxis1);
|
|
Vector3f hingeAxisWorld = Stack.alloc(Vector3f.class);
|
rbAFrame.basis.getColumn(2, hingeAxisWorld);
|
centerOfMassA.basis.transform(hingeAxisWorld);
|
|
mat1.transpose(centerOfMassA.basis);
|
mat2.transpose(centerOfMassB.basis);
|
jacAng[0].init(jointAxis0,
|
mat1,
|
mat2,
|
rbA.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)),
|
rbB.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)));
|
|
// JAVA NOTE: reused mat1 and mat2, as recomputation is not needed
|
jacAng[1].init(jointAxis1,
|
mat1,
|
mat2,
|
rbA.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)),
|
rbB.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)));
|
|
// JAVA NOTE: reused mat1 and mat2, as recomputation is not needed
|
jacAng[2].init(hingeAxisWorld,
|
mat1,
|
mat2,
|
rbA.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)),
|
rbB.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)));
|
|
// Compute limit information
|
float hingeAngle = getHingeAngle();
|
|
//set bias, sign, clear accumulator
|
correction = 0f;
|
limitSign = 0f;
|
solveLimit = false;
|
accLimitImpulse = 0f;
|
|
if (lowerLimit < upperLimit) {
|
if (hingeAngle <= lowerLimit * limitSoftness) {
|
correction = (lowerLimit - hingeAngle);
|
limitSign = 1.0f;
|
solveLimit = true;
|
}
|
else if (hingeAngle >= upperLimit * limitSoftness) {
|
correction = upperLimit - hingeAngle;
|
limitSign = -1.0f;
|
solveLimit = true;
|
}
|
}
|
|
// Compute K = J*W*J' for hinge axis
|
Vector3f axisA = Stack.alloc(Vector3f.class);
|
rbAFrame.basis.getColumn(2, axisA);
|
centerOfMassA.basis.transform(axisA);
|
|
kHinge = 1.0f / (getRigidBodyA().computeAngularImpulseDenominator(axisA) +
|
getRigidBodyB().computeAngularImpulseDenominator(axisA));
|
}
|
|
@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 centerOfMassA = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
Transform centerOfMassB = rbB.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
|
Vector3f pivotAInW = (Vector3f) Stack.alloc(rbAFrame.origin);
|
centerOfMassA.transform(pivotAInW);
|
|
Vector3f pivotBInW = (Vector3f) Stack.alloc(rbBFrame.origin);
|
centerOfMassB.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
|
|
// get axes in world space
|
Vector3f axisA = Stack.alloc(Vector3f.class);
|
rbAFrame.basis.getColumn(2, axisA);
|
centerOfMassA.basis.transform(axisA);
|
|
Vector3f axisB = Stack.alloc(Vector3f.class);
|
rbBFrame.basis.getColumn(2, axisB);
|
centerOfMassB.basis.transform(axisB);
|
|
Vector3f angVelA = getRigidBodyA().getAngularVelocity(Stack.alloc(Vector3f.class));
|
Vector3f angVelB = getRigidBodyB().getAngularVelocity(Stack.alloc(Vector3f.class));
|
|
Vector3f angVelAroundHingeAxisA = Stack.alloc(Vector3f.class);
|
angVelAroundHingeAxisA.scale(axisA.dot(angVelA), axisA);
|
|
Vector3f angVelAroundHingeAxisB = Stack.alloc(Vector3f.class);
|
angVelAroundHingeAxisB.scale(axisB.dot(angVelB), axisB);
|
|
Vector3f angAorthog = Stack.alloc(Vector3f.class);
|
angAorthog.sub(angVelA, angVelAroundHingeAxisA);
|
|
Vector3f angBorthog = Stack.alloc(Vector3f.class);
|
angBorthog.sub(angVelB, angVelAroundHingeAxisB);
|
|
Vector3f velrelOrthog = Stack.alloc(Vector3f.class);
|
velrelOrthog.sub(angAorthog, angBorthog);
|
|
{
|
// solve orthogonal angular velocity correction
|
float relaxation = 1f;
|
float len = velrelOrthog.length();
|
if (len > 0.00001f) {
|
Vector3f normal = Stack.alloc(Vector3f.class);
|
normal.normalize(velrelOrthog);
|
|
float denom = getRigidBodyA().computeAngularImpulseDenominator(normal) +
|
getRigidBodyB().computeAngularImpulseDenominator(normal);
|
// scale for mass and relaxation
|
// todo: expose this 0.9 factor to developer
|
velrelOrthog.scale((1f / denom) * relaxationFactor);
|
}
|
|
// solve angular positional correction
|
// TODO: check
|
//Vector3f angularError = -axisA.cross(axisB) *(btScalar(1.)/timeStep);
|
Vector3f angularError = Stack.alloc(Vector3f.class);
|
angularError.cross(axisA, axisB);
|
angularError.negate();
|
angularError.scale(1f / timeStep);
|
float len2 = angularError.length();
|
if (len2 > 0.00001f) {
|
Vector3f normal2 = Stack.alloc(Vector3f.class);
|
normal2.normalize(angularError);
|
|
float denom2 = getRigidBodyA().computeAngularImpulseDenominator(normal2) +
|
getRigidBodyB().computeAngularImpulseDenominator(normal2);
|
angularError.scale((1f / denom2) * relaxation);
|
}
|
|
tmp.negate(velrelOrthog);
|
tmp.add(angularError);
|
rbA.applyTorqueImpulse(tmp);
|
|
tmp.sub(velrelOrthog, angularError);
|
rbB.applyTorqueImpulse(tmp);
|
|
// solve limit
|
if (solveLimit) {
|
tmp.sub(angVelB, angVelA);
|
float amplitude = ((tmp).dot(axisA) * relaxationFactor + correction * (1f / timeStep) * biasFactor) * limitSign;
|
|
float impulseMag = amplitude * kHinge;
|
|
// Clamp the accumulated impulse
|
float temp = accLimitImpulse;
|
accLimitImpulse = Math.max(accLimitImpulse + impulseMag, 0f);
|
impulseMag = accLimitImpulse - temp;
|
|
Vector3f impulse = Stack.alloc(Vector3f.class);
|
impulse.scale(impulseMag * limitSign, axisA);
|
|
rbA.applyTorqueImpulse(impulse);
|
|
tmp.negate(impulse);
|
rbB.applyTorqueImpulse(tmp);
|
}
|
}
|
|
// apply motor
|
if (enableAngularMotor) {
|
// todo: add limits too
|
Vector3f angularLimit = Stack.alloc(Vector3f.class);
|
angularLimit.set(0f, 0f, 0f);
|
|
Vector3f velrel = Stack.alloc(Vector3f.class);
|
velrel.sub(angVelAroundHingeAxisA, angVelAroundHingeAxisB);
|
float projRelVel = velrel.dot(axisA);
|
|
float desiredMotorVel = motorTargetVelocity;
|
float motor_relvel = desiredMotorVel - projRelVel;
|
|
float unclippedMotorImpulse = kHinge * motor_relvel;
|
// todo: should clip against accumulated impulse
|
float clippedMotorImpulse = unclippedMotorImpulse > maxMotorImpulse ? maxMotorImpulse : unclippedMotorImpulse;
|
clippedMotorImpulse = clippedMotorImpulse < -maxMotorImpulse ? -maxMotorImpulse : clippedMotorImpulse;
|
Vector3f motorImp = Stack.alloc(Vector3f.class);
|
motorImp.scale(clippedMotorImpulse, axisA);
|
|
tmp.add(motorImp, angularLimit);
|
rbA.applyTorqueImpulse(tmp);
|
|
tmp.negate(motorImp);
|
tmp.sub(angularLimit);
|
rbB.applyTorqueImpulse(tmp);
|
}
|
}
|
}
|
|
public void updateRHS(float timeStep) {
|
}
|
|
public float getHingeAngle() {
|
Transform centerOfMassA = rbA.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
Transform centerOfMassB = rbB.getCenterOfMassTransform(Stack.alloc(Transform.class));
|
|
Vector3f refAxis0 = Stack.alloc(Vector3f.class);
|
rbAFrame.basis.getColumn(0, refAxis0);
|
centerOfMassA.basis.transform(refAxis0);
|
|
Vector3f refAxis1 = Stack.alloc(Vector3f.class);
|
rbAFrame.basis.getColumn(1, refAxis1);
|
centerOfMassA.basis.transform(refAxis1);
|
|
Vector3f swingAxis = Stack.alloc(Vector3f.class);
|
rbBFrame.basis.getColumn(1, swingAxis);
|
centerOfMassB.basis.transform(swingAxis);
|
|
return ScalarUtil.atan2Fast(swingAxis.dot(refAxis0), swingAxis.dot(refAxis1));
|
}
|
|
public void setAngularOnly(boolean angularOnly) {
|
this.angularOnly = angularOnly;
|
}
|
|
public void enableAngularMotor(boolean enableMotor, float targetVelocity, float maxMotorImpulse) {
|
this.enableAngularMotor = enableMotor;
|
this.motorTargetVelocity = targetVelocity;
|
this.maxMotorImpulse = maxMotorImpulse;
|
}
|
|
public void setLimit(float low, float high) {
|
setLimit(low, high, 0.9f, 0.3f, 1.0f);
|
}
|
|
public void setLimit(float low, float high, float _softness, float _biasFactor, float _relaxationFactor) {
|
lowerLimit = low;
|
upperLimit = high;
|
|
limitSoftness = _softness;
|
biasFactor = _biasFactor;
|
relaxationFactor = _relaxationFactor;
|
}
|
|
public float getLowerLimit() {
|
return lowerLimit;
|
}
|
|
public float getUpperLimit() {
|
return upperLimit;
|
}
|
|
public Transform getAFrame(Transform out) {
|
out.set(rbAFrame);
|
return out;
|
}
|
|
public Transform getBFrame(Transform out) {
|
out.set(rbBFrame);
|
return out;
|
}
|
|
public boolean getSolveLimit() {
|
return solveLimit;
|
}
|
|
public float getLimitSign() {
|
return limitSign;
|
}
|
|
public boolean getAngularOnly() {
|
return angularOnly;
|
}
|
|
public boolean getEnableAngularMotor() {
|
return enableAngularMotor;
|
}
|
|
public float getMotorTargetVelosity() {
|
return motorTargetVelocity;
|
}
|
|
public float getMaxMotorImpulse() {
|
return maxMotorImpulse;
|
}
|
|
}
|