module.exports = Equation;
var JacobianElement = require('../math/JacobianElement'),
Vec3 = require('../math/Vec3');
/**
* Equation base class
* @class Equation
* @constructor
* @author schteppe
* @param {Body} bi
* @param {Body} bj
* @param {Number} minForce Minimum (read: negative max) force to be applied by the constraint.
* @param {Number} maxForce Maximum (read: positive max) force to be applied by the constraint.
*/
function Equation(bi,bj,minForce,maxForce){
this.id = Equation.id++;
/**
* @property {number} minForce
*/
this.minForce = typeof(minForce)==="undefined" ? -1e6 : minForce;
/**
* @property {number} maxForce
*/
this.maxForce = typeof(maxForce)==="undefined" ? 1e6 : maxForce;
/**
* @property bi
* @type {Body}
*/
this.bi = bi;
/**
* @property bj
* @type {Body}
*/
this.bj = bj;
/**
* SPOOK parameter
* @property {number} a
*/
this.a = 0.0;
/**
* SPOOK parameter
* @property {number} b
*/
this.b = 0.0;
/**
* SPOOK parameter
* @property {number} eps
*/
this.eps = 0.0;
/**
* @property {JacobianElement} jacobianElementA
*/
this.jacobianElementA = new JacobianElement();
/**
* @property {JacobianElement} jacobianElementB
*/
this.jacobianElementB = new JacobianElement();
/**
* @property {boolean} enabled
* @default true
*/
this.enabled = true;
// Set typical spook params
this.setSpookParams(1e7,4,1/60);
}
Equation.prototype.constructor = Equation;
Equation.id = 0;
/**
* Recalculates a,b,eps.
* @method setSpookParams
*/
Equation.prototype.setSpookParams = function(stiffness,relaxation,timeStep){
var d = relaxation,
k = stiffness,
h = timeStep;
this.a = 4.0 / (h * (1 + 4 * d));
this.b = (4.0 * d) / (1 + 4 * d);
this.eps = 4.0 / (h * h * k * (1 + 4 * d));
};
/**
* Computes the RHS of the SPOOK equation
* @method computeB
* @return {Number}
*/
Equation.prototype.computeB = function(a,b,h){
var GW = this.computeGW(),
Gq = this.computeGq(),
GiMf = this.computeGiMf();
return - Gq * a - GW * b - GiMf*h;
};
/**
* Computes G*q, where q are the generalized body coordinates
* @method computeGq
* @return {Number}
*/
Equation.prototype.computeGq = function(){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
xi = bi.position,
xj = bj.position;
return GA.spatial.dot(xi) + GB.spatial.dot(xj);
};
var zero = new Vec3();
/**
* Computes G*W, where W are the body velocities
* @method computeGW
* @return {Number}
*/
Equation.prototype.computeGW = function(){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
vi = bi.velocity,
vj = bj.velocity,
wi = bi.angularVelocity || zero,
wj = bj.angularVelocity || zero;
return GA.multiplyVectors(vi,wi) + GB.multiplyVectors(vj,wj);
};
/**
* Computes G*Wlambda, where W are the body velocities
* @method computeGWlambda
* @return {Number}
*/
Equation.prototype.computeGWlambda = function(){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
vi = bi.vlambda,
vj = bj.vlambda,
wi = bi.wlambda || zero,
wj = bj.wlambda || zero;
return GA.multiplyVectors(vi,wi) + GB.multiplyVectors(vj,wj);
};
/**
* Computes G*inv(M)*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies.
* @method computeGiMf
* @return {Number}
*/
var iMfi = new Vec3(),
iMfj = new Vec3(),
invIi_vmult_taui = new Vec3(),
invIj_vmult_tauj = new Vec3();
Equation.prototype.computeGiMf = function(){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
fi = bi.force,
ti = bi.torque,
fj = bj.force,
tj = bj.torque,
invMassi = bi.invMassSolve,
invMassj = bj.invMassSolve;
if(bi.invInertiaWorldSolve){ bi.invInertiaWorldSolve.vmult(ti,invIi_vmult_taui); }
else { invIi_vmult_taui.set(0,0,0); }
if(bj.invInertiaWorldSolve){ bj.invInertiaWorldSolve.vmult(tj,invIj_vmult_tauj); }
else { invIj_vmult_tauj.set(0,0,0); }
fi.mult(invMassi,iMfi);
fj.mult(invMassj,iMfj);
return GA.multiplyVectors(iMfi,invIi_vmult_taui) + GB.multiplyVectors(iMfj,invIj_vmult_tauj);
};
/**
* Computes G*inv(M)*G'
* @method computeGiMGt
* @return {Number}
*/
var tmp = new Vec3();
Equation.prototype.computeGiMGt = function(){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
invMassi = bi.invMassSolve,
invMassj = bj.invMassSolve,
invIi = bi.invInertiaWorldSolve,
invIj = bj.invInertiaWorldSolve,
result = invMassi + invMassj;
if(invIi){
invIi.vmult(GA.rotational,tmp);
result += tmp.dot(GA.rotational);
}
if(invIj){
invIj.vmult(GB.rotational,tmp);
result += tmp.dot(GB.rotational);
}
return result;
};
var addToWlambda_temp = new Vec3(),
addToWlambda_Gi = new Vec3(),
addToWlambda_Gj = new Vec3(),
addToWlambda_ri = new Vec3(),
addToWlambda_rj = new Vec3(),
addToWlambda_Mdiag = new Vec3();
/**
* Add constraint velocity to the bodies.
* @method addToWlambda
* @param {Number} deltalambda
*/
Equation.prototype.addToWlambda = function(deltalambda){
var GA = this.jacobianElementA,
GB = this.jacobianElementB,
bi = this.bi,
bj = this.bj,
temp = addToWlambda_temp;
// Add to linear velocity
// v_lambda += inv(M) * delta_lamba * G
GA.spatial.mult(bi.invMassSolve * deltalambda,temp);
bi.vlambda.vadd(temp, bi.vlambda);
GB.spatial.mult(bj.invMassSolve * deltalambda,temp);
bj.vlambda.vadd(temp, bj.vlambda);
// Add to angular velocity
if(bi.invInertiaWorldSolve){
bi.invInertiaWorldSolve.vmult(GA.rotational,temp);
temp.mult(deltalambda,temp);
bi.wlambda.vadd(temp,bi.wlambda);
}
if(bj.invInertiaWorldSolve){
bj.invInertiaWorldSolve.vmult(GB.rotational,temp);
temp.mult(deltalambda,temp);
bj.wlambda.vadd(temp,bj.wlambda);
}
};
/**
* Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps
* @method computeInvC
* @param {Number} eps
* @return {Number}
*/
Equation.prototype.computeC = function(){
return this.computeGiMGt() + this.eps;
};