Started revamp of the Tilemap system. Also removed old 'Advanced Physics' and dropped in p2.js which is what I hope we'll eventually use.

This commit is contained in:
photonstorm
2013-10-11 04:42:11 +01:00
parent a7230aa769
commit b868c2cb1b
72 changed files with 6704 additions and 6454 deletions
@@ -0,0 +1,42 @@
module.exports = Constraint;
/**
* Base constraint class.
*
* @class Constraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
*/
function Constraint(bodyA,bodyB){
/**
* Equations to be solved in this constraint
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* First body participating in the constraint.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second body participating in the constraint.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
};
/**
* To be implemented by subclasses. Should update the internal constraint parameters.
* @method update
*/
/*Constraint.prototype.update = function(){
throw new Error("method update() not implmemented in this Constraint subclass!");
};*/
@@ -0,0 +1,136 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2'),
mat2 = require('../math/mat2');
module.exports = ContactEquation;
/**
* Non-penetration constraint equation.
*
* @class ContactEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function ContactEquation(bi,bj){
Equation.call(this,bi,bj,0,1e6);
this.ri = vec2.create();
this.penetrationVec = vec2.create();
this.rj = vec2.create();
this.ni = vec2.create();
this.rixn = 0;
this.rjxn = 0;
};
ContactEquation.prototype = new Equation();
ContactEquation.prototype.constructor = ContactEquation;
ContactEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
xi = bi.position,
xj = bj.position;
var vi = bi.velocity,
wi = bi.angularVelocity,
fi = bi.force,
taui = bi.angularForce;
var vj = bj.velocity,
wj = bj.angularVelocity,
fj = bj.force,
tauj = bj.angularForce;
var penetrationVec = this.penetrationVec,
invMassi = bi.invMass,
invMassj = bj.invMass,
invIi = bi.invInertia,
invIj = bj.invInertia,
n = this.ni;
// Caluclate cross products
this.rixn = vec2.crossLength(ri,n);
this.rjxn = vec2.crossLength(rj,n);
// Calculate q = xj+rj -(xi+ri) i.e. the penetration vector
vec2.add(penetrationVec,xj,rj);
vec2.sub(penetrationVec,penetrationVec,xi);
vec2.sub(penetrationVec,penetrationVec,ri);
var Gq = vec2.dot(n,penetrationVec);
// Compute iteration
var GW = vec2.dot(vj,n) - vec2.dot(vi,n) + wj * this.rjxn - wi * this.rixn;
var GiMf = vec2.dot(fj,n)*invMassj - vec2.dot(fi,n)*invMassi + invIj*tauj*this.rjxn - invIi*taui*this.rixn;
var B = - Gq * a - GW * b - h*GiMf;
return B;
};
// Compute C = GMG+eps in the SPOOK equation
var computeC_tmp1 = vec2.create(),
tmpMat1 = mat2.create(),
tmpMat2 = mat2.create();
ContactEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj,
n = this.ni,
rixn = this.rixn,
rjxn = this.rjxn,
tmp = computeC_tmp1,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
var C = vec2.dot(n,vec2.transformMat2(tmp,n,imMat1)) + vec2.dot(n,vec2.transformMat2(tmp,n,imMat2)) + eps;
//var C = bi.invMass + bj.invMass + eps;
C += bi.invInertia * this.rixn * this.rixn;
C += bj.invInertia * this.rjxn * this.rjxn;
return C;
};
ContactEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj,
n = this.ni,
dot = vec2.dot;
return dot(n, bj.vlambda) + bj.wlambda * this.rjxn - dot(n, bi.vlambda) - bi.wlambda * this.rixn;
};
var addToWlambda_temp = vec2.create();
ContactEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj,
n = this.ni,
temp = addToWlambda_temp,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
// Add to linear velocity
//vec2.scale(temp,n,-bi.invMass*deltalambda);
vec2.scale(temp,vec2.transformMat2(temp,n,imMat1),-deltalambda);
vec2.add( bi.vlambda,bi.vlambda, temp );
//vec2.scale(temp,n,bj.invMass*deltalambda);
vec2.scale(temp,vec2.transformMat2(temp,n,imMat2),deltalambda);
vec2.add( bj.vlambda,bj.vlambda, temp);
// Add to angular velocity
bi.wlambda -= bi.invInertia * this.rixn * deltalambda;
bj.wlambda += bj.invInertia * this.rjxn * deltalambda;
};
@@ -0,0 +1,62 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, vec2 = require('../math/vec2')
module.exports = DistanceConstraint;
/**
* Constraint that tries to keep the distance between two bodies constant.
*
* @class DistanceConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {number} dist The distance to keep between the bodies.
* @param {number} maxForce
* @extends {Constraint}
*/
function DistanceConstraint(bodyA,bodyB,distance,maxForce){
Constraint.call(this,bodyA,bodyB);
this.distance = distance;
if(typeof(maxForce)==="undefined" ) {
maxForce = 1e6;
}
var normal = new ContactEquation(bodyA,bodyB); // Just in the normal direction
this.equations = [ normal ];
// Make the contact constraint bilateral
this.setMaxForce(maxForce);
}
DistanceConstraint.prototype = new Constraint();
/**
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
DistanceConstraint.prototype.update = function(){
var normal = this.equations[0],
bodyA = this.bodyA,
bodyB = this.bodyB,
distance = this.distance;
vec2.sub(normal.ni, bodyB.position, bodyA.position);
vec2.normalize(normal.ni,normal.ni);
vec2.scale(normal.ri, normal.ni, distance*0.5);
vec2.scale(normal.rj, normal.ni, -distance*0.5);
};
DistanceConstraint.prototype.setMaxForce = function(f){
var normal = this.equations[0];
normal.minForce = -f;
normal.maxForce = f;
};
DistanceConstraint.prototype.getMaxForce = function(f){
var normal = this.equations[0];
return normal.maxForce;
};
@@ -0,0 +1,77 @@
module.exports = Equation;
/**
* Base class for constraint equations.
* @class Equation
* @constructor
* @param {Body} bi First body participating in the equation
* @param {Body} bj Second body participating in the equation
* @param {number} minForce Minimum force to apply. Default: -1e6
* @param {number} maxForce Maximum force to apply. Default: 1e6
*/
function Equation(bi,bj,minForce,maxForce){
/**
* Minimum force to apply when solving
* @property minForce
* @type {Number}
*/
this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce;
/**
* Max force to apply when solving
* @property maxForce
* @type {Number}
*/
this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce;
/**
* First body participating in the constraint
* @property bi
* @type {Body}
*/
this.bi = bi;
/**
* Second body participating in the constraint
* @property bj
* @type {Body}
*/
this.bj = bj;
/**
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
* @property stiffness
* @type {Number}
*/
this.stiffness = 1e6;
/**
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
* @property relaxation
* @type {Number}
*/
this.relaxation = 4;
this.a = 0;
this.b = 0;
this.eps = 0;
this.h = 0;
this.updateSpookParams(1/60);
};
Equation.prototype.constructor = Equation;
/**
* Update SPOOK parameters .a, .b and .eps according to the given time step. See equations 9, 10 and 11 in the <a href="http://www8.cs.umu.se/kurser/5DV058/VT09/lectures/spooknotes.pdf">SPOOK notes</a>.
* @method updateSpookParams
* @param {number} timeStep
*/
Equation.prototype.updateSpookParams = function(timeStep){
var k = this.stiffness,
d = this.relaxation,
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));
this.h = timeStep;
};
@@ -0,0 +1,180 @@
var mat2 = require('../math/mat2')
, vec2 = require('../math/vec2')
, Equation = require('./Equation')
module.exports = FrictionEquation;
// 3D cross product from glmatrix, until we get this to work...
function cross(out, a, b) {
var ax = a[0], ay = a[1], az = a[2],
bx = b[0], by = b[1], bz = b[2];
out[0] = ay * bz - az * by;
out[1] = az * bx - ax * bz;
out[2] = ax * by - ay * bx;
return out;
};
var dot = vec2.dot;
/**
* Constrains the slipping in a contact along a tangent
*
* @class FrictionEquation
* @constructor
* @param {Body} bi
* @param {Body} bj
* @param {Number} slipForce
* @extends {Equation}
*/
function FrictionEquation(bi,bj,slipForce){
Equation.call(this,bi,bj,-slipForce,slipForce);
/**
* Relative vector from center of body i to the contact point, in world coords.
* @property ri
* @type {Float32Array}
*/
this.ri = vec2.create();
/**
* Relative vector from center of body j to the contact point, in world coords.
* @property rj
* @type {Float32Array}
*/
this.rj = vec2.create();
/**
* Tangent vector that the friction force will act along, in world coords.
* @property t
* @type {Float32Array}
*/
this.t = vec2.create();
this.rixt = 0;
this.rjxt = 0;
};
FrictionEquation.prototype = new Equation();
FrictionEquation.prototype.constructor = FrictionEquation;
/**
* Set the slipping condition for the constraint. The friction force cannot be
* larger than this value.
* @method setSlipForce
* @param {Number} slipForce
*/
FrictionEquation.prototype.setSlipForce = function(slipForce){
this.maxForce = slipForce;
this.minForce = -slipForce;
};
var rixtVec = [0,0,0];
var rjxtVec = [0,0,0];
var ri3 = [0,0,0];
var rj3 = [0,0,0];
var t3 = [0,0,0];
FrictionEquation.prototype.computeB = function(a,b,h){
var a = this.a,
b = this.b,
bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
t = this.t;
// Caluclate cross products
ri3[0] = ri[0];
ri3[1] = ri[1];
rj3[0] = rj[0];
rj3[1] = rj[1];
t3[0] = t[0];
t3[1] = t[1];
cross(rixtVec, ri3, t3);//ri.cross(t,rixt);
cross(rjxtVec, rj3, t3);//rj.cross(t,rjxt);
this.rixt = rixtVec[2];
this.rjxt = rjxtVec[2];
var GW = -dot(bi.velocity,t) + dot(bj.velocity,t) - this.rixt*bi.angularVelocity + this.rjxt*bj.angularVelocity; // eq. 40
var GiMf = -dot(bi.force,t)*bi.invMass +dot(bj.force,t)*bj.invMass -this.rixt*bi.invInertia*bi.angularForce + this.rjxt*bj.invInertia*bj.angularForce;
var B = /* - Gq * a */ - GW * b - h*GiMf;
return B;
};
// Compute C = G * iM * G' + eps
//
// G*iM*G' =
//
// [ iM1 ] [-t ]
// [-t (-ri x t) t (rj x t)] * [ iI1 ] [-ri x t]
// [ iM2 ] [t ]
// [ iI2 ] [rj x t ]
//
// = (-t)*iM1*(-t) + (-ri x t)*iI1*(-ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
//
// = t*iM1*t + (ri x t)*iI1*(ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
//
var computeC_tmp1 = vec2.create(),
tmpMat1 = mat2.create(),
tmpMat2 = mat2.create();
FrictionEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj,
t = this.t,
C = 0.0,
tmp = computeC_tmp1,
imMat1 = tmpMat1,
imMat2 = tmpMat2,
dot = vec2.dot;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
C = dot(t,vec2.transformMat2(tmp,t,imMat1)) + dot(t,vec2.transformMat2(tmp,t,imMat2)) + eps;
//C = bi.invMass + bj.invMass + eps;
C += bi.invInertia * this.rixt * this.rixt;
C += bj.invInertia * this.rjxt * this.rjxt;
return C;
};
FrictionEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj,
t = this.t,
dot = vec2.dot;
return dot(t, bj.vlambda) + bj.wlambda * this.rjxt - bi.wlambda * this.rixt - dot(t, bi.vlambda);
};
var FrictionEquation_addToWlambda_tmp = vec2.create();
FrictionEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj,
t = this.t,
tmp = FrictionEquation_addToWlambda_tmp,
imMat1 = tmpMat1,
imMat2 = tmpMat2;
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat1),-deltalambda);
//vec2.scale(tmp, t, -bi.invMass * deltalambda); //t.mult(invMassi * deltalambda, tmp);
vec2.add(bi.vlambda, bi.vlambda, tmp); //bi.vlambda.vsub(tmp,bi.vlambda);
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat2),deltalambda);
//vec2.scale(tmp, t, bj.invMass * deltalambda); //t.mult(invMassj * deltalambda, tmp);
vec2.add(bj.vlambda, bj.vlambda, tmp); //bj.vlambda.vadd(tmp,bj.vlambda);
bi.wlambda -= bi.invInertia * this.rixt * deltalambda;
bj.wlambda += bj.invInertia * this.rjxt * deltalambda;
};
@@ -0,0 +1,94 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, RotationalVelocityEquation = require('./RotationalVelocityEquation')
, vec2 = require('../math/vec2')
module.exports = PointToPointConstraint;
/**
* Connects two bodies at given offset points
* @class PointToPointConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to.
* @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point.
* @param {Float32Array} pivotB See pivotA.
* @param {Number} maxForce The maximum force that should be applied to constrain the bodies.
* @extends {Constraint}
* @todo Ability to specify world points
*/
function PointToPointConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
Constraint.call(this,bodyA,bodyB);
maxForce = typeof(maxForce)!="undefined" ? maxForce : 1e7;
this.pivotA = pivotA;
this.pivotB = pivotB;
// Equations to be fed to the solver
var eqs = this.equations = [
new ContactEquation(bodyA,bodyB), // Normal
new ContactEquation(bodyA,bodyB), // Tangent
];
var normal = eqs[0];
var tangent = eqs[1];
tangent.minForce = normal.minForce = -maxForce;
tangent.maxForce = normal.maxForce = maxForce;
this.motorEquation = null;
}
PointToPointConstraint.prototype = new Constraint();
PointToPointConstraint.prototype.update = function(){
var bodyA = this.bodyA,
bodyB = this.bodyB,
pivotA = this.pivotA,
pivotB = this.pivotB,
eqs = this.equations,
normal = eqs[0],
tangent= eqs[1];
vec2.subtract(normal.ni, bodyB.position, bodyA.position);
vec2.normalize(normal.ni,normal.ni);
vec2.rotate(normal.ri, pivotA, bodyA.angle);
vec2.rotate(normal.rj, pivotB, bodyB.angle);
vec2.rotate(tangent.ni, normal.ni, Math.PI / 2);
vec2.copy(tangent.ri, normal.ri);
vec2.copy(tangent.rj, normal.rj);
};
/**
* Enable the rotational motor
* @method enableMotor
*/
PointToPointConstraint.prototype.enableMotor = function(){
if(this.motorEquation) return;
this.motorEquation = new RotationalVelocityEquation(this.bodyA,this.bodyB);
this.equations.push(this.motorEquation);
};
/**
* Disable the rotational motor
* @method disableMotor
*/
PointToPointConstraint.prototype.disableMotor = function(){
if(!this.motorEquation) return;
var i = this.equations.indexOf(this.motorEquation);
this.motorEquation = null;
this.equations.splice(i,1);
};
/**
* Set the speed of the rotational constraint motor
* @method setMotorSpeed
* @param {Number} speed
*/
PointToPointConstraint.prototype.setMotorSpeed = function(speed){
if(!this.motorEquation) return;
var i = this.equations.indexOf(this.motorEquation);
this.equations[i].relativeVelocity = speed;
};
@@ -0,0 +1,83 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, vec2 = require('../math/vec2')
module.exports = PrismaticConstraint;
/**
* Constraint that only allows translation along a line between the bodies, no rotation
*
* @class PrismaticConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {Object} options
* @param {Number} options.maxForce
* @param {Array} options.worldAxis
* @param {Array} options.localAxisA
* @param {Array} options.localAxisB
* @extends {Constraint}
*/
function PrismaticConstraint(bodyA,bodyB,options){
options = options || {};
Constraint.call(this,bodyA,bodyB);
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : 1e6;
// Equations to be fed to the solver
var eqs = this.equations = [
new ContactEquation(bodyA,bodyB), // Tangent for bodyA
new ContactEquation(bodyB,bodyA), // Tangent for bodyB
];
var tangentA = eqs[0],
tangentB = eqs[1];
tangentA.minForce = tangentB.minForce = -maxForce;
tangentA.maxForce = tangentB.maxForce = maxForce;
var worldAxis = vec2.create();
if(options.worldAxis){
vec2.copy(worldAxis, options.worldAxis);
} else {
vec2.sub(worldAxis, bodyB.position, bodyA.position);
}
vec2.normalize(worldAxis,worldAxis);
// Axis that is local in each body
this.localAxisA = vec2.create();
this.localAxisB = vec2.create();
if(options.localAxisA) vec2.copy(this.localAxisA, options.localAxisA);
else vec2.rotate(this.localAxisA, worldAxis, -bodyA.angle);
if(options.localAxisB) vec2.copy(this.localAxisB, options.localAxisB);
else vec2.rotate(this.localAxisB, worldAxis, -bodyB.angle);
}
PrismaticConstraint.prototype = new Constraint();
/**
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
PrismaticConstraint.prototype.update = function(){
var tangentA = this.equations[0],
tangentB = this.equations[1],
bodyA = this.bodyA,
bodyB = this.bodyB;
// Get tangent directions
vec2.rotate(tangentA.ni, this.localAxisA, bodyA.angle - Math.PI/2);
vec2.rotate(tangentB.ni, this.localAxisB, bodyB.angle + Math.PI/2);
// Get distance vector
var dist = vec2.create();
vec2.sub(dist, bodyB.position, bodyA.position);
vec2.scale(tangentA.ri, tangentA.ni, -vec2.dot(tangentA.ni, dist));
vec2.scale(tangentB.ri, tangentB.ni, vec2.dot(tangentB.ni, dist));
vec2.add(tangentA.rj, tangentA.ri, dist);
vec2.sub(tangentB.rj, tangentB.ri, dist);
vec2.set(tangentA.ri, 0, 0);
vec2.set(tangentB.ri, 0, 0);
};
@@ -0,0 +1,70 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2');
module.exports = RotationalVelocityEquation;
/**
* Syncs rotational velocity of two bodies, or sets a relative velocity (motor).
*
* @class RotationalVelocityEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function RotationalVelocityEquation(bi,bj){
Equation.call(this,bi,bj,-1e6,1e6);
this.relativeVelocity = 1;
this.ratio = 1;
};
RotationalVelocityEquation.prototype = new Equation();
RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation;
RotationalVelocityEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
vi = bi.velocity,
wi = bi.angularVelocity,
taui = bi.angularForce,
vj = bj.velocity,
wj = bj.angularVelocity,
tauj = bj.angularForce,
invIi = bi.invInertia,
invIj = bj.invInertia,
Gq = 0,
GW = this.ratio * wj - wi + this.relativeVelocity,
GiMf = invIj*tauj - invIi*taui;
var B = - Gq * a - GW * b - h*GiMf;
return B;
};
// Compute C = GMG+eps in the SPOOK equation
RotationalVelocityEquation.prototype.computeC = function(eps){
var bi = this.bi,
bj = this.bj;
var C = bi.invInertia + bj.invInertia + eps;
return C;
};
var computeGWlambda_ulambda = vec2.create();
RotationalVelocityEquation.prototype.computeGWlambda = function(){
var bi = this.bi,
bj = this.bj;
var GWlambda = bj.wlambda - bi.wlambda;
return GWlambda;
};
var addToWlambda_temp = vec2.create();
RotationalVelocityEquation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj;
// Add to angular velocity
bi.wlambda -= bi.invInertia * deltalambda;
bj.wlambda += bj.invInertia * deltalambda;
};