Updated p2.js to latest build.

This commit is contained in:
photonstorm 2013-12-11 03:19:49 +00:00
parent 215632c0a7
commit da5d8c9272
49 changed files with 2758 additions and 1282 deletions

View file

@ -57,6 +57,8 @@ Updates:
* Updated display/fullscreen example to reflect new full screen change.
* Loads of updates to the TypeScript definitions files - games fully compile now and lots of missing classes added :)
* Removed 'null parent' check from Group constructor. Will parent to game.world only if parent value is undefined.
* The tutorials have now been translated into Spanish - thanks feiss :)
* separateY updated to re-implement the 'riding platforms' special condition (thanks cocoademon)
Bug Fixes:

View file

@ -0,0 +1,38 @@
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create });
function preload() {
game.load.image('arrow', 'assets/sprites/arrow.png');
}
var sprite;
var tween;
function create() {
sprite = game.add.sprite(32, 32, 'arrow');
sprite.anchor.setTo(0.5, 0.5);
game.input.onDown.add(moveSprite, this);
}
function moveSprite (pointer) {
if (tween && tween.isRunning)
{
tween.stop();
}
sprite.rotation = game.physics.angleToPointer(sprite, pointer);
// 300 = 300 pixels per second = the speed the sprite will move at, regardless of the distance it has to travel
var duration = (game.physics.distanceToPointer(sprite, pointer) / 300) * 1000;
tween = game.add.tween(sprite).to({ x: pointer.x, y: pointer.y }, duration, Phaser.Easing.Linear.None, true);
}

View file

@ -1,6 +1,4 @@
var vec2 = require('../math/vec2')
, Nearphase = require('./Nearphase')
, Shape = require('./../shapes/Shape')
module.exports = Broadphase;
@ -10,6 +8,12 @@ module.exports = Broadphase;
* @constructor
*/
function Broadphase(){
/**
* The resulting overlapping pairs. Will be filled with results during .getCollisionPairs().
* @property result
* @type {Array}
*/
this.result = [];
};
@ -23,10 +27,7 @@ Broadphase.prototype.getCollisionPairs = function(world){
throw new Error("getCollisionPairs must be implemented in a subclass!");
};
// Temp things
var dist = vec2.create(),
worldNormal = vec2.create(),
yAxis = vec2.fromValues(0,1);
var dist = vec2.create();
/**
* Check whether the bounding radius of two bodies overlap.

View file

@ -104,7 +104,7 @@ GridBroadphase.prototype.getCollisionPairs = function(world){
}
} else if(si instanceof Plane){
// Put in all bins for now
if(bi.angle === 0){
if(bi.angle == 0){
var y = bi.position[1];
for(var j=0; j!==Nbins && ymin+binsizeY*(j-1)<y; j++){
for(var k=0; k<nx; k++){

View file

@ -6,8 +6,9 @@ var vec2 = require('../math/vec2')
, ContactEquation = require('../constraints/ContactEquation')
, FrictionEquation = require('../constraints/FrictionEquation')
, Circle = require('../shapes/Circle')
, Shape = require('../shapes/Shape')
module.exports = Nearphase;
module.exports = Narrowphase;
// Temp things
var yAxis = vec2.fromValues(0,1);
@ -27,27 +28,104 @@ var tmp1 = vec2.fromValues(0,0)
, tmp13 = vec2.fromValues(0,0)
, tmp14 = vec2.fromValues(0,0)
, tmp15 = vec2.fromValues(0,0)
, tmp16 = vec2.fromValues(0,0)
, tmp17 = vec2.fromValues(0,0)
, tmp18 = vec2.fromValues(0,0)
/**
* Nearphase. Creates contacts and friction given shapes and transforms.
* @class Nearphase
* Narrowphase. Creates contacts and friction given shapes and transforms.
* @class Narrowphase
* @constructor
*/
function Nearphase(){
function Narrowphase(){
/**
* @property contactEquations
* @type {Array}
*/
this.contactEquations = [];
/**
* @property frictionEquations
* @type {Array}
*/
this.frictionEquations = [];
/**
* Whether to make friction equations in the upcoming contacts.
* @property enableFriction
* @type {Boolean}
*/
this.enableFriction = true;
/**
* The friction slip force to use when creating friction equations.
* @property slipForce
* @type {Number}
*/
this.slipForce = 10.0;
/**
* The friction value to use in the upcoming friction equations.
* @property frictionCoefficient
* @type {Number}
*/
this.frictionCoefficient = 0.3;
this.reuseObjects = true;
this.reusableContactEquations = [];
this.reusableFrictionEquations = [];
/**
* The restitution value to use in the next contact equations.
* @property restitution
* @type {Number}
*/
this.restitution = 0;
// Keep track of the colliding bodies last step
this.collidingBodiesLastStep = {};
};
/**
* Check if the bodies were in contact since the last reset().
* @method collidedLastStep
* @param {Body} bi
* @param {Body} bj
* @return {Boolean}
*/
Narrowphase.prototype.collidedLastStep = function(bi,bj){
var id1 = bi.id,
id2 = bj.id;
if(id1 > id2){
var tmp = id1;
id1 = id2;
id2 = tmp;
}
return !!this.collidingBodiesLastStep[id1 + " " + id2];
};
/**
* Throws away the old equatons and gets ready to create new
* @method reset
*/
Nearphase.prototype.reset = function(){
Narrowphase.prototype.reset = function(){
// Save the colliding bodies data
for(var key in this.collidingBodiesLastStep)
delete this.collidingBodiesLastStep[key];
for(var i=0; i!==this.contactEquations.length; i++){
var eq = this.contactEquations[i],
id1 = eq.bi.id,
id2 = eq.bj.id;
if(id1 > id2){
var tmp = id1;
id1 = id2;
id2 = tmp;
}
this.collidingBodiesLastStep[id1 + " " + id2] = true;
}
if(this.reuseObjects){
var ce = this.contactEquations,
fe = this.frictionEquations,
@ -68,10 +146,14 @@ Nearphase.prototype.reset = function(){
* @param {Body} bodyB
* @return {ContactEquation}
*/
Nearphase.prototype.createContactEquation = function(bodyA,bodyB){
Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){
var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB);
c.bi = bodyA;
c.bj = bodyB;
c.shapeA = shapeA;
c.shapeB = shapeB;
c.restitution = this.restitution;
c.firstImpact = !this.collidedLastStep(bodyA,bodyB);
return c;
};
@ -82,11 +164,14 @@ Nearphase.prototype.createContactEquation = function(bodyA,bodyB){
* @param {Body} bodyB
* @return {FrictionEquation}
*/
Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){
Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){
var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB);
c.bi = bodyA;
c.bj = bodyB;
c.shapeA = shapeA;
c.shapeB = shapeB;
c.setSlipForce(this.slipForce);
c.frictionCoefficient = this.frictionCoefficient;
return c;
};
@ -96,16 +181,17 @@ Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){
* @param {ContactEquation} contactEquation
* @return {FrictionEquation}
*/
Nearphase.prototype.createFrictionFromContact = function(c){
Narrowphase.prototype.createFrictionFromContact = function(c){
var eq = this.createFrictionEquation(c.bi,c.bj);
vec2.copy(eq.ri, c.ri);
vec2.copy(eq.rj, c.rj);
vec2.rotate(eq.t, c.ni, -Math.PI / 2);
eq.contactEquation = c;
return eq;
}
/**
* Plane/line nearphase
* Plane/line Narrowphase
* @method planeLine
* @param {Body} bi
* @param {Plane} si
@ -116,7 +202,8 @@ Nearphase.prototype.createFrictionFromContact = function(c){
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
Narrowphase.prototype[Shape.PLANE | Shape.LINE] =
Narrowphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
var lineShape = sj,
lineAngle = aj,
lineBody = bj,
@ -170,7 +257,7 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
if(d < 0){
var c = this.createContactEquation(planeBody,lineBody);
var c = this.createContactEquation(planeBody,lineBody,si,sj);
vec2.copy(c.ni, worldNormal);
vec2.normalize(c.ni,c.ni);
@ -197,12 +284,13 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
}
};
Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
Narrowphase.prototype[Shape.PARTICLE | Shape.CAPSULE] =
Narrowphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius, 0);
};
/**
* Circle/line nearphase
* Circle/line Narrowphase
* @method circleLine
* @param {Body} bi
* @param {Circle} si
@ -216,7 +304,8 @@ Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTes
* @param {Number} lineRadius Radius to add to the line. Can be used to test Capsules.
* @param {Number} circleRadius If set, this value overrides the circle shape radius.
*/
Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){
Narrowphase.prototype[Shape.CIRCLE | Shape.LINE] =
Narrowphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){
var lineShape = sj,
lineAngle = aj,
lineBody = bj,
@ -293,7 +382,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
if(justTest) return true;
var c = this.createContactEquation(circleBody,lineBody);
var c = this.createContactEquation(circleBody,lineBody,si,sj);
vec2.scale(c.ni, orthoDist, -1);
vec2.normalize(c.ni, c.ni);
@ -328,7 +417,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
if(justTest) return true;
var c = this.createContactEquation(circleBody,lineBody);
var c = this.createContactEquation(circleBody,lineBody,si,sj);
vec2.copy(c.ni, dist);
vec2.normalize(c.ni,c.ni);
@ -358,7 +447,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
};
/**
* Circle/capsule nearphase
* Circle/capsule Narrowphase
* @method circleCapsule
* @param {Body} bi
* @param {Circle} si
@ -369,12 +458,13 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
Narrowphase.prototype[Shape.CIRCLE | Shape.CAPSULE] =
Narrowphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius);
};
/**
* Circle/convex nearphase
* Circle/convex Narrowphase
* @method circleConvex
* @param {Body} bi
* @param {Circle} si
@ -385,14 +475,16 @@ Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest)
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
Narrowphase.prototype[Shape.CIRCLE | Shape.CONVEX] =
Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, circleRadius){
var convexShape = sj,
convexAngle = aj,
convexBody = bj,
convexOffset = xj,
circleOffset = xi,
circleBody = bi,
circleShape = si;
circleShape = si,
circleRadius = typeof(circleRadius)=="number" ? circleRadius : circleShape.radius;
var worldVertex0 = tmp1,
worldVertex1 = tmp2,
@ -404,14 +496,31 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
orthoDist = tmp8,
projectedPoint = tmp9,
dist = tmp10,
worldVertex = tmp11;
worldVertex = tmp11,
closestEdge = -1,
closestEdgeDistance = null,
closestEdgeOrthoDist = tmp12,
closestEdgeProjectedPoint = tmp13,
candidate = tmp14,
candidateDist = tmp15,
minCandidate = tmp16,
found = false,
minCandidateDistance = Number.MAX_VALUE;
var numReported = 0;
// New algorithm:
// 1. Check so center of circle is not inside the polygon. If it is, this wont work...
// 2. For each edge
// 2. 1. Get point on circle that is closest to the edge (scale normal with -radius)
// 2. 2. Check if point is inside.
verts = convexShape.vertices;
// Check all edges first
for(var i=0; i<verts.length; i++){
for(var i=0; i!==verts.length; i++){
var v0 = verts[i],
v1 = verts[(i+1)%verts.length];
@ -426,44 +535,135 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
// Get tangent to the edge. Points out of the Convex
vec2.rotate(worldTangent, worldEdgeUnit, -Math.PI/2);
// Check distance from the plane spanned by the edge vs the circle
sub(dist, circleOffset, worldVertex0);
var d = dot(dist, worldTangent);
sub(centerDist, worldVertex0, convexOffset);
// Get point on circle, closest to the polygon
vec2.scale(candidate,worldTangent,-circleShape.radius);
add(candidate,candidate,circleOffset);
sub(convexToCircle, circleOffset, convexOffset);
if(pointInConvex(candidate,convexShape,convexOffset,convexAngle)){
if(d < circleShape.radius && dot(centerDist,convexToCircle) > 0){
vec2.sub(candidateDist,worldVertex0,candidate);
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent));
// Now project the circle onto the edge
vec2.scale(orthoDist, worldTangent, d);
sub(projectedPoint, circleOffset, orthoDist);
/*
// Check distance from the plane spanned by the edge vs the circle
sub(dist, circleOffset, worldVertex0);
var d = dot(dist, worldTangent);
sub(centerDist, worldVertex0, convexOffset);
// Check if the point is within the edge span
var pos = dot(worldEdgeUnit, projectedPoint);
var pos0 = dot(worldEdgeUnit, worldVertex0);
var pos1 = dot(worldEdgeUnit, worldVertex1);
sub(convexToCircle, circleOffset, convexOffset);
if(pos > pos0 && pos < pos1){
// We got contact!
if(d < circleRadius && dot(centerDist,convexToCircle) > 0){
var c = this.createContactEquation(circleBody,convexBody);
// Now project the circle onto the edge
vec2.scale(orthoDist, worldTangent, d);
sub(projectedPoint, circleOffset, orthoDist);
vec2.scale(c.ni, orthoDist, -1);
vec2.normalize(c.ni, c.ni);
vec2.scale( c.ri, c.ni, circleShape.radius);
// Check if the point is within the edge span
var pos = dot(worldEdgeUnit, projectedPoint);
var pos0 = dot(worldEdgeUnit, worldVertex0);
var pos1 = dot(worldEdgeUnit, worldVertex1);
if(pos > pos0 && pos < pos1){
// We got contact!
if(justTest) return true;
if(closestEdgeDistance === null || d*d<closestEdgeDistance*closestEdgeDistance){
closestEdgeDistance = d;
closestEdge = i;
vec2.copy(closestEdgeOrthoDist, orthoDist);
vec2.copy(closestEdgeProjectedPoint, projectedPoint);
}
}
}
*/
if(candidateDistance < minCandidateDistance){
vec2.copy(minCandidate,candidate);
minCandidateDistance = candidateDistance;
vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance);
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,candidate);
found = true;
}
}
}
if(found){
var c = this.createContactEquation(circleBody,convexBody,si,sj);
vec2.sub(c.ni, minCandidate, circleOffset)
vec2.normalize(c.ni, c.ni);
vec2.scale(c.ri, c.ni, circleRadius);
add(c.ri, c.ri, circleOffset);
sub(c.ri, c.ri, circleBody.position);
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
add(c.rj, c.rj, convexOffset);
sub(c.rj, c.rj, convexBody.position);
this.contactEquations.push(c);
if(this.enableFriction)
this.frictionEquations.push( this.createFrictionFromContact(c) );
return true;
}
/*
if(closestEdge != -1){
var c = this.createContactEquation(circleBody,convexBody);
vec2.scale(c.ni, closestEdgeOrthoDist, -1);
vec2.normalize(c.ni, c.ni);
vec2.scale(c.ri, c.ni, circleRadius);
add(c.ri, c.ri, circleOffset);
sub(c.ri, c.ri, circleBody.position);
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
add(c.rj, c.rj, convexOffset);
sub(c.rj, c.rj, convexBody.position);
this.contactEquations.push(c);
if(this.enableFriction)
this.frictionEquations.push( this.createFrictionFromContact(c) );
return true;
}
*/
// Check all vertices
if(circleRadius > 0){
for(var i=0; i<verts.length; i++){
var localVertex = verts[i];
vec2.rotate(worldVertex, localVertex, convexAngle);
add(worldVertex, worldVertex, convexOffset);
sub(dist, worldVertex, circleOffset);
if(vec2.squaredLength(dist) < circleRadius*circleRadius){
if(justTest) return true;
var c = this.createContactEquation(circleBody,convexBody,si,sj);
vec2.copy(c.ni, dist);
vec2.normalize(c.ni,c.ni);
// Vector from circle to contact point is the normal times the circle radius
vec2.scale(c.ri, c.ni, circleRadius);
add(c.ri, c.ri, circleOffset);
sub(c.ri, c.ri, circleBody.position);
sub( c.rj, projectedPoint, convexOffset);
sub(c.rj, worldVertex, convexOffset);
add(c.rj, c.rj, convexOffset);
sub(c.rj, c.rj, convexBody.position);
this.contactEquations.push(c);
if(this.enableFriction){
this.frictionEquations.push( this.createFrictionFromContact(c) );
this.frictionEquations.push(this.createFrictionFromContact(c));
}
return true;
@ -471,43 +671,50 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
}
}
// Check all vertices
for(var i=0; i<verts.length; i++){
var localVertex = verts[i];
vec2.rotate(worldVertex, localVertex, convexAngle);
add(worldVertex, worldVertex, convexOffset);
sub(dist, worldVertex, circleOffset);
if(vec2.squaredLength(dist) < circleShape.radius*circleShape.radius){
var c = this.createContactEquation(circleBody,convexBody);
vec2.copy(c.ni, dist);
vec2.normalize(c.ni,c.ni);
// Vector from circle to contact point is the normal times the circle radius
vec2.scale(c.ri, c.ni, circleShape.radius);
add(c.ri, c.ri, circleOffset);
sub(c.ri, c.ri, circleBody.position);
sub(c.rj, worldVertex, convexOffset);
add(c.rj, c.rj, convexOffset);
sub(c.rj, c.rj, convexBody.position);
this.contactEquations.push(c);
if(this.enableFriction){
this.frictionEquations.push(this.createFrictionFromContact(c));
}
return true;
}
}
return false;
};
// Check if a point is in a polygon
var pic_worldVertex0 = vec2.create(),
pic_worldVertex1 = vec2.create(),
pic_r0 = vec2.create(),
pic_r1 = vec2.create();
function pointInConvex(worldPoint,convexShape,convexOffset,convexAngle){
var worldVertex0 = pic_worldVertex0,
worldVertex1 = pic_worldVertex1,
r0 = pic_r0,
r1 = pic_r1,
point = worldPoint,
verts = convexShape.vertices,
lastCross = null;
for(var i=0; i!==verts.length+1; i++){
var v0 = verts[i%verts.length],
v1 = verts[(i+1)%verts.length];
// Transform vertices to world
// can we instead transform point to local of the convex???
vec2.rotate(worldVertex0, v0, convexAngle);
vec2.rotate(worldVertex1, v1, convexAngle);
add(worldVertex0, worldVertex0, convexOffset);
add(worldVertex1, worldVertex1, convexOffset);
sub(r0, worldVertex0, point);
sub(r1, worldVertex1, point);
var cross = vec2.crossLength(r0,r1);
if(lastCross===null) lastCross = cross;
// If we got a different sign of the distance vector, the point is out of the polygon
if(cross*lastCross <= 0){
return false;
}
lastCross = cross;
}
return true;
};
/**
* Particle/convex nearphase
* Particle/convex Narrowphase
* @method particleConvex
* @param {Body} bi
* @param {Particle} si
@ -517,8 +724,10 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
* @param {Convex} sj
* @param {Array} xj
* @param {Number} aj
* @todo use pointInConvex and code more similar to circleConvex
*/
Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
Narrowphase.prototype[Shape.PARTICLE | Shape.CONVEX] =
Narrowphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
var convexShape = sj,
convexAngle = aj,
convexBody = bj,
@ -540,15 +749,26 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
closestEdge = -1,
closestEdgeDistance = null,
closestEdgeOrthoDist = tmp12,
closestEdgeProjectedPoint = tmp13;
closestEdgeProjectedPoint = tmp13,
r0 = tmp14, // vector from particle to vertex0
r1 = tmp15,
localPoint = tmp16,
candidateDist = tmp17,
minEdgeNormal = tmp18,
minCandidateDistance = Number.MAX_VALUE;
var numReported = 0;
var numReported = 0,
found = false,
verts = convexShape.vertices;
verts = convexShape.vertices;
// Check if the particle is in the polygon at all
if(!pointInConvex(particleOffset,convexShape,convexOffset,convexAngle))
return false;
// Check all edges first
for(var i=0; i<verts.length; i++){
var v0 = verts[i],
// Check edges first
var lastCross = null;
for(var i=0; i!==verts.length+1; i++){
var v0 = verts[i%verts.length],
v1 = verts[(i+1)%verts.length];
// Transform vertices to world
@ -571,6 +791,8 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
sub(convexToparticle, particleOffset, convexOffset);
/*
if(d < 0 && dot(centerDist,convexToparticle) >= 0){
// Now project the particle onto the edge
@ -594,18 +816,32 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
}
}
}
*/
vec2.sub(candidateDist,worldVertex0,particleOffset);
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent));
if(candidateDistance < minCandidateDistance){
minCandidateDistance = candidateDistance;
vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance);
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,particleOffset);
vec2.copy(minEdgeNormal,worldTangent);
found = true;
}
}
if(closestEdge != -1){
var c = this.createContactEquation(particleBody,convexBody);
if(found){
var c = this.createContactEquation(particleBody,convexBody,si,sj);
vec2.copy(c.ni, closestEdgeOrthoDist);
vec2.scale(c.ni, minEdgeNormal, -1);
vec2.normalize(c.ni, c.ni);
// Particle has no extent to the contact point
vec2.set(c.ri, 0, 0);
add(c.ri, c.ri, particleOffset);
sub(c.ri, c.ri, particleBody.position);
// From convex center to point
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
add(c.rj, c.rj, convexOffset);
sub(c.rj, c.rj, convexBody.position);
@ -623,7 +859,7 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
};
/**
* Circle/circle nearphase
* Circle/circle Narrowphase
* @method circleCircle
* @param {Body} bi
* @param {Circle} si
@ -634,7 +870,8 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){
Narrowphase.prototype[Shape.CIRCLE] =
Narrowphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){
var bodyA = bi,
shapeA = si,
offsetA = xi,
@ -651,7 +888,7 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
if(justTest) return true;
var c = this.createContactEquation(bodyA,bodyB);
var c = this.createContactEquation(bodyA,bodyB,si,sj);
sub(c.ni, offsetB, offsetA);
vec2.normalize(c.ni,c.ni);
@ -673,26 +910,27 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
};
/**
* Convex/Plane nearphase
* @method convexPlane
* Plane/Convex Narrowphase
* @method planeConvex
* @param {Body} bi
* @param {Convex} si
* @param {Plane} si
* @param {Array} xi
* @param {Number} ai
* @param {Body} bj
* @param {Plane} sj
* @param {Convex} sj
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
var convexBody = bi,
convexOffset = xi,
convexShape = si,
convexAngle = ai,
planeBody = bj,
planeShape = sj,
planeOffset = xj,
planeAngle = aj;
Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] =
Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
var convexBody = bj,
convexOffset = xj,
convexShape = sj,
convexAngle = aj,
planeBody = bi,
planeShape = si,
planeOffset = xi,
planeAngle = ai;
var worldVertex = tmp1,
worldNormal = tmp2,
@ -701,8 +939,8 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
var numReported = 0;
vec2.rotate(worldNormal, yAxis, planeAngle);
for(var i=0; i<si.vertices.length; i++){
var v = si.vertices[i];
for(var i=0; i<convexShape.vertices.length; i++){
var v = convexShape.vertices[i];
vec2.rotate(worldVertex, v, convexAngle);
add(worldVertex, worldVertex, convexOffset);
@ -713,7 +951,7 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
// Found vertex
numReported++;
var c = this.createContactEquation(planeBody,convexBody);
var c = this.createContactEquation(planeBody,convexBody,planeShape,convexShape);
sub(dist, worldVertex, planeOffset);
@ -746,7 +984,16 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
};
/**
* Nearphase for particle vs plane
* @method convexPlane
* @deprecated Use .planeConvex() instead!
*/
Narrowphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
console.warn("Narrowphase.prototype.convexPlane is deprecated. Use planeConvex instead!");
return this.planeConvex( bj,sj,xj,aj, bi,si,xi,ai );
}
/**
* Narrowphase for particle vs plane
* @method particlePlane
* @param {Body} bi The particle body
* @param {Particle} si Particle shape
@ -757,7 +1004,8 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
* @param {Array} xj World position for the plane
* @param {Number} aj World angle for the plane
*/
Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
Narrowphase.prototype[Shape.PARTICLE | Shape.PLANE] =
Narrowphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
var particleBody = bi,
particleShape = si,
particleOffset = xi,
@ -779,7 +1027,7 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
if(d > 0) return false;
if(justTest) return true;
var c = this.createContactEquation(planeBody,particleBody);
var c = this.createContactEquation(planeBody,particleBody,sj,si);
vec2.copy(c.ni, worldNormal);
vec2.scale( dist, c.ni, d );
@ -801,7 +1049,7 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
};
/**
* Circle/Particle nearphase
* Circle/Particle Narrowphase
* @method circleParticle
* @param {Body} bi
* @param {Circle} si
@ -812,7 +1060,8 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] =
Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
var circleBody = bi,
circleShape = si,
circleOffset = xi,
@ -825,7 +1074,7 @@ Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justT
if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return false;
if(justTest) return true;
var c = this.createContactEquation(circleBody,particleBody);
var c = this.createContactEquation(circleBody,particleBody,si,sj);
vec2.copy(c.ni, dist);
vec2.normalize(c.ni,c.ni);
@ -850,28 +1099,39 @@ var capsulePlane_tmpCircle = new Circle(1),
capsulePlane_tmp1 = vec2.create(),
capsulePlane_tmp2 = vec2.create(),
capsulePlane_tmp3 = vec2.create();
Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] =
Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj ){
var end1 = capsulePlane_tmp1,
end2 = capsulePlane_tmp2,
circle = capsulePlane_tmpCircle,
dst = capsulePlane_tmp3;
// Compute world end positions
vec2.set(end1, -si.length/2, 0);
vec2.rotate(end1,end1,ai);
add(end1,end1,xi);
vec2.set(end1, -sj.length/2, 0);
vec2.rotate(end1,end1,aj);
add(end1,end1,xj);
vec2.set(end2, si.length/2, 0);
vec2.rotate(end2,end2,ai);
add(end2,end2,xi);
vec2.set(end2, sj.length/2, 0);
vec2.rotate(end2,end2,aj);
add(end2,end2,xj);
circle.radius = si.radius;
circle.radius = sj.radius;
// Do nearphase as two circles
this.circlePlane(bi,circle,end1,0, bj,sj,xj,aj);
this.circlePlane(bi,circle,end2,0, bj,sj,xj,aj);
// Do Narrowphase as two circles
this.circlePlane(bj,circle,end1,0, bi,si,xi,ai);
this.circlePlane(bj,circle,end2,0, bi,si,xi,ai);
};
/**
* @method capsulePlane
* @deprecated Use .planeCapsule() instead!
*/
Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!");
return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai );
}
/**
* Creates ContactEquations and FrictionEquations for a collision.
* @method circlePlane
@ -883,7 +1143,8 @@ Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
* @param {Array} xj Extra offset for the plane shape.
* @param {Number} aj Extra angle to apply to the plane
*/
Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] =
Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
var circleBody = bi,
circleShape = si,
circleOffset = xi, // Offset from body center, rotated!
@ -910,7 +1171,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
if(d > circleShape.radius) return false; // No overlap. Abort.
// Create contact
var contact = this.createContactEquation(planeBody,circleBody);
var contact = this.createContactEquation(planeBody,circleBody,sj,si);
// ni is the plane world normal
vec2.copy(contact.ni, worldNormal);
@ -937,7 +1198,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
/**
* Convex/convex nearphase.See <a href="http://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/">this article</a> for more info.
* Convex/convex Narrowphase.See <a href="http://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/">this article</a> for more info.
* @method convexConvex
* @param {Body} bi
* @param {Convex} si
@ -948,7 +1209,8 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
* @param {Array} xj
* @param {Number} aj
*/
Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
Narrowphase.prototype[Shape.CONVEX] =
Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
var sepAxis = tmp1,
worldPoint = tmp2,
worldPoint0 = tmp3,
@ -959,7 +1221,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
dist = tmp8,
worldNormal = tmp9;
var found = Nearphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis);
var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis);
if(!found) return false;
// Make sure the separating axis is directed from shape i to shape j
@ -969,8 +1231,8 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
}
// Find edges with normals closest to the separating axis
var closestEdge1 = Nearphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis
closestEdge2 = Nearphase.getClosestEdge(sj,aj,sepAxis);
var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis
closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis);
if(closestEdge1==-1 || closestEdge2==-1) return false;
@ -1036,7 +1298,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
// Project it to the center edge and use the projection direction as normal
// Create contact
var c = this.createContactEquation(bodyA,bodyB);
var c = this.createContactEquation(bodyA,bodyB,si,sj);
// Get center edge from body A
var v0 = shapeA.vertices[(closestEdgeA) % shapeA.vertices.length],
@ -1090,7 +1352,7 @@ var pcoa_tmp1 = vec2.fromValues(0,0);
* @param {Array} worldAxis
* @param {Array} result
*/
Nearphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){
Narrowphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){
var max=null,
min=null,
v,
@ -1141,7 +1403,7 @@ var fsa_tmp1 = vec2.fromValues(0,0)
* @param {Array} sepAxis The resulting axis
* @return {Boolean} Whether the axis could be found.
*/
Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){
Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){
var maxDist = null,
overlap = false,
found = false,
@ -1172,8 +1434,8 @@ Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepA
vec2.normalize(normal,normal);
// Project hulls onto that normal
Nearphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1);
Nearphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2);
Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1);
Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2);
// Order by span position
var a=span1,
@ -1215,7 +1477,7 @@ var gce_tmp1 = vec2.fromValues(0,0)
* @param {Boolean} flip
* @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed.
*/
Nearphase.getClosestEdge = function(c,angle,axis,flip){
Narrowphase.getClosestEdge = function(c,angle,axis,flip){
var localAxis = gce_tmp1,
edge = gce_tmp2,
normal = gce_tmp3;

View file

@ -27,10 +27,10 @@ function QuadTree(bounds, pointQuad, maxDepth, maxChildren){
}
/**
* The root node of the QuadTree which covers the entire area being segmented.
* @property root
* @type Node
*/
* The root node of the QuadTree which covers the entire area being segmented.
* @property root
* @type Node
*/
this.root = node;
}
@ -299,7 +299,7 @@ BoundsNode.prototype.insert = function(item){
console.log("radius:",item.boundingRadius);
console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width);
console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height);
*/
*/
//todo: make _bounds bounds
if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child

View file

@ -19,24 +19,24 @@ function SAP1DBroadphase(world){
Broadphase.apply(this);
/**
* List of bodies currently in the broadphase.
* @property axisList
* @type {Array}
*/
* List of bodies currently in the broadphase.
* @property axisList
* @type {Array}
*/
this.axisList = world.bodies.slice(0);
/**
* The world to search in.
* @property world
* @type {World}
*/
* The world to search in.
* @property world
* @type {World}
*/
this.world = world;
/**
* Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on.
* @property axisIndex
* @type {Number}
*/
* Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on.
* @property axisIndex
* @type {Number}
*/
this.axisIndex = 0;
// Add listeners to update the list of bodies.
@ -92,23 +92,13 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){
// Look through the list
for(i=0, N=bodies.length; i!==N; i++){
var bi = bodies[i],
biPos = bi.position[axisIndex],
ri = bi.boundingRadius;
var bi = bodies[i];
for(j=i+1; j<N; j++){
var bj = bodies[j],
bjPos = bj.position[axisIndex],
rj = bj.boundingRadius,
boundA1 = biPos-ri,
boundA2 = biPos+ri,
boundB1 = bjPos-rj,
boundB2 = bjPos+rj;
var bj = bodies[j];
// Abort if we got gap til the next body
if( boundB1 > boundA2 ){
if(!SAP1DBroadphase.checkBounds(bi,bj,axisIndex))
break;
}
// If we got overlap, add pair
if(Broadphase.boundingRadiusCheck(bi,bj))
@ -118,3 +108,25 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){
return result;
};
/**
* Check if the bounds of two bodies overlap, along the given SAP axis.
* @static
* @method checkBounds
* @param {Body} bi
* @param {Body} bj
* @param {Number} axisIndex
* @return {Boolean}
*/
SAP1DBroadphase.checkBounds = function(bi,bj,axisIndex){
var biPos = bi.position[axisIndex],
ri = bi.boundingRadius,
bjPos = bj.position[axisIndex],
rj = bj.boundingRadius,
boundA1 = biPos-ri,
boundA2 = biPos+ri,
boundB1 = bjPos-rj,
boundB2 = bjPos+rj;
return boundB1 < boundA2;
};

View file

@ -12,24 +12,24 @@ module.exports = Constraint;
function Constraint(bodyA,bodyB){
/**
* Equations to be solved in this constraint
* @property equations
* @type {Array}
*/
* Equations to be solved in this constraint
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* First body participating in the constraint.
* @property bodyA
* @type {Body}
*/
* First body participating in the constraint.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second body participating in the constraint.
* @property bodyB
* @type {Body}
*/
* Second body participating in the constraint.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
};

View file

@ -5,7 +5,7 @@ var Equation = require("./Equation"),
module.exports = ContactEquation;
/**
* Non-penetration constraint equation.
* Non-penetration constraint equation. Tries to make the ri and rj vectors the same point.
*
* @class ContactEquation
* @constructor
@ -14,13 +14,57 @@ module.exports = ContactEquation;
* @param {Body} bj
*/
function ContactEquation(bi,bj){
Equation.call(this,bi,bj,0,1e6);
Equation.call(this,bi,bj,0,Number.MAX_VALUE);
/**
* Vector from body i center of mass to the contact point.
* @property ri
* @type {Array}
*/
this.ri = vec2.create();
this.penetrationVec = vec2.create();
/**
* Vector from body j center of mass to the contact point.
* @property rj
* @type {Array}
*/
this.rj = vec2.create();
/**
* The normal vector, pointing out of body i
* @property ni
* @type {Array}
*/
this.ni = vec2.create();
this.rixn = 0;
this.rjxn = 0;
/**
* The restitution to use. 0=no bounciness, 1=max bounciness.
* @property restitution
* @type {Number}
*/
this.restitution = 0;
/**
* Set to true if this is the first impact between the bodies (not persistant contact).
* @property firstImpact
* @type {Boolean}
*/
this.firstImpact = false;
/**
* The shape in body i that triggered this contact.
* @property shapeA
* @type {Shape}
*/
this.shapeA = null;
/**
* The shape in body j that triggered this contact.
* @property shapeB
* @type {Shape}
*/
this.shapeB = null;
};
ContactEquation.prototype = new Equation();
ContactEquation.prototype.constructor = ContactEquation;
@ -32,105 +76,39 @@ ContactEquation.prototype.computeB = function(a,b,h){
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;
n = this.ni,
G = this.G;
// Caluclate cross products
this.rixn = vec2.crossLength(ri,n);
this.rjxn = vec2.crossLength(rj,n);
var rixn = vec2.crossLength(ri,n),
rjxn = vec2.crossLength(rj,n);
// G = [-n -rixn n rjxn]
G[0] = -n[0];
G[1] = -n[1];
G[2] = -rixn;
G[3] = n[0];
G[4] = n[1];
G[5] = rjxn;
// 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 GW, Gq;
if(this.firstImpact && this.restitution !== 0){
Gq = 0;
GW = (1/b)*(1+this.restitution) * this.computeGW();
} else {
GW = this.computeGW();
Gq = vec2.dot(n,penetrationVec);
}
var GiMf = this.computeGiMf();
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;
};

View file

@ -1,5 +1,5 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, Equation = require('./Equation')
, vec2 = require('../math/vec2')
module.exports = DistanceConstraint;
@ -19,16 +19,25 @@ module.exports = DistanceConstraint;
function DistanceConstraint(bodyA,bodyB,distance,maxForce){
Constraint.call(this,bodyA,bodyB);
/**
* The distance to keep.
* @property distance
* @type {Number}
*/
this.distance = distance;
if(typeof(maxForce)==="undefined" ) {
maxForce = 1e6;
}
var normal = new ContactEquation(bodyA,bodyB); // Just in the normal direction
if(typeof(maxForce)==="undefined" )
maxForce = Number.MAX_VALUE;
var normal = new Equation(bodyA,bodyB,-maxForce,maxForce); // Just in the normal direction
this.equations = [ normal ];
var r = vec2.create();
normal.computeGq = function(){
vec2.sub(r, bodyB.position, bodyA.position);
return vec2.length(r)-distance;
};
// Make the contact constraint bilateral
this.setMaxForce(maxForce);
}
@ -38,24 +47,38 @@ DistanceConstraint.prototype = new Constraint();
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
var n = vec2.create();
DistanceConstraint.prototype.update = function(){
var normal = this.equations[0],
bodyA = this.bodyA,
bodyB = this.bodyB,
distance = this.distance;
distance = this.distance,
G = normal.G;
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);
vec2.sub(n, bodyB.position, bodyA.position);
vec2.normalize(n,n);
G[0] = -n[0];
G[1] = -n[1];
G[3] = n[0];
G[4] = n[1];
};
/**
* Set the max force to be used
* @method setMaxForce
* @param {Number} f
*/
DistanceConstraint.prototype.setMaxForce = function(f){
var normal = this.equations[0];
normal.minForce = -f;
normal.maxForce = f;
};
/**
* Get the max force
* @method getMaxForce
* @return {Number}
*/
DistanceConstraint.prototype.getMaxForce = function(f){
var normal = this.equations[0];
return normal.maxForce;

View file

@ -1,5 +1,9 @@
module.exports = Equation;
var vec2 = require('../math/vec2'),
mat2 = require('../math/mat2'),
Utils = require('../utils/Utils');
/**
* Base class for constraint equations.
* @class Equation
@ -12,52 +16,75 @@ module.exports = Equation;
function Equation(bi,bj,minForce,maxForce){
/**
* Minimum force to apply when solving
* @property minForce
* @type {Number}
*/
* 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}
*/
* 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}
*/
* First body participating in the constraint
* @property bi
* @type {Body}
*/
this.bi = bi;
/**
* Second body participating in the constraint
* @property bj
* @type {Body}
*/
* 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}
*/
* 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}
*/
* 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;
/**
* The Jacobian entry of this equation. 6 numbers, 3 per body (x,y,angle).
* @property G
* @type {Array}
*/
this.G = new Utils.ARRAY_TYPE(6);
// Constraint frames for body i and j
/*
this.xi = vec2.create();
this.xj = vec2.create();
this.ai = 0;
this.aj = 0;
*/
this.offset = 0;
this.a = 0;
this.b = 0;
this.eps = 0;
this.h = 0;
this.updateSpookParams(1/60);
/**
* The resulting constraint multiplier from the last solve. This is mostly equivalent to the force produced by the constraint.
* @property multiplier
* @type {Number}
*/
this.multiplier = 0;
};
Equation.prototype.constructor = Equation;
@ -75,3 +102,215 @@ Equation.prototype.updateSpookParams = function(timeStep){
this.eps = 4.0 / (h * h * k * (1 + 4 * d));
this.h = timeStep;
};
function Gmult(G,vi,wi,vj,wj){
return G[0] * vi[0] +
G[1] * vi[1] +
G[2] * wi +
G[3] * vj[0] +
G[4] * vj[1] +
G[5] * wj;
}
/**
* Computes the RHS of the SPOOK equation
* @method computeB
* @return {Number}
*/
Equation.prototype.computeB = function(a,b,h){
var GW = this.computeGW();
var Gq = this.computeGq();
var GiMf = this.computeGiMf();
return - Gq * a - GW * b - GiMf*h;
};
/**
* Computes G*q, where q are the generalized body coordinates
* @method computeGq
* @return {Number}
*/
var qi = vec2.create(),
qj = vec2.create();
Equation.prototype.computeGq = function(){
var G = this.G,
bi = this.bi,
bj = this.bj,
xi = bi.position,
xj = bj.position,
ai = bi.angle,
aj = bj.angle;
// Transform to the given body frames
/*
vec2.rotate(qi,this.xi,ai);
vec2.rotate(qj,this.xj,aj);
vec2.add(qi,qi,xi);
vec2.add(qj,qj,xj);
*/
return Gmult(G, qi, ai, qj, aj) + this.offset;
};
var tmp_i = vec2.create(),
tmp_j = vec2.create();
Equation.prototype.transformedGmult = function(G,vi,wi,vj,wj){
// Transform velocity to the given body frames
// v_p = v + w x r
/*
vec2.rotate(tmp_i,this.xi,Math.PI / 2 + this.bi.angle); // Get r, and rotate 90 degrees. We get the "x r" part
vec2.rotate(tmp_j,this.xj,Math.PI / 2 + this.bj.angle);
vec2.scale(tmp_i,tmp_i,wi); // Temp vectors are now (w x r)
vec2.scale(tmp_j,tmp_j,wj);
vec2.add(tmp_i,tmp_i,vi);
vec2.add(tmp_j,tmp_j,vj);
*/
// Note: angular velocity is same
return Gmult(G,vi,wi,vj,wj);
};
/**
* Computes G*W, where W are the body velocities
* @method computeGW
* @return {Number}
*/
Equation.prototype.computeGW = function(){
var G = this.G,
bi = this.bi,
bj = this.bj,
vi = bi.velocity,
vj = bj.velocity,
wi = bi.angularVelocity,
wj = bj.angularVelocity;
return this.transformedGmult(G,vi,wi,vj,wj);
};
/**
* Computes G*Wlambda, where W are the body velocities
* @method computeGWlambda
* @return {Number}
*/
Equation.prototype.computeGWlambda = function(){
var G = this.G,
bi = this.bi,
bj = this.bj,
vi = bi.vlambda,
vj = bj.vlambda,
wi = bi.wlambda,
wj = bj.wlambda;
return this.transformedGmult(G,vi,wi,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 = vec2.create(),
iMfj = vec2.create();
Equation.prototype.computeGiMf = function(){
var bi = this.bi,
bj = this.bj,
fi = bi.force,
ti = bi.angularForce,
fj = bj.force,
tj = bj.angularForce,
invMassi = bi.invMass,
invMassj = bj.invMass,
invIi = bi.invInertia,
invIj = bj.invInertia,
G = this.G;
vec2.scale(iMfi, fi,invMassi);
vec2.scale(iMfj, fj,invMassj);
return this.transformedGmult(G,iMfi,ti*invIi,iMfj,tj*invIj);
};
/**
* Computes G*inv(M)*G'
* @method computeGiMGt
* @return {Number}
*/
Equation.prototype.computeGiMGt = function(){
var bi = this.bi,
bj = this.bj,
invMassi = bi.invMass,
invMassj = bj.invMass,
invIi = bi.invInertia,
invIj = bj.invInertia,
G = this.G;
return G[0] * G[0] * invMassi +
G[1] * G[1] * invMassi +
G[2] * G[2] * invIi +
G[3] * G[3] * invMassj +
G[4] * G[4] * invMassj +
G[5] * G[5] * invIj;
};
var addToWlambda_temp = vec2.create(),
addToWlambda_Gi = vec2.create(),
addToWlambda_Gj = vec2.create(),
addToWlambda_ri = vec2.create(),
addToWlambda_rj = vec2.create();
var tmpMat1 = mat2.create(),
tmpMat2 = mat2.create();
/**
* Add constraint velocity to the bodies.
* @method addToWlambda
* @param {Number} deltalambda
*/
Equation.prototype.addToWlambda = function(deltalambda){
var bi = this.bi,
bj = this.bj,
temp = addToWlambda_temp,
imMat1 = tmpMat1,
imMat2 = tmpMat2,
Gi = addToWlambda_Gi,
Gj = addToWlambda_Gj,
ri = addToWlambda_ri,
rj = addToWlambda_rj,
G = this.G;
Gi[0] = G[0];
Gi[1] = G[1];
Gj[0] = G[3];
Gj[1] = G[4];
mat2.identity(imMat1);
mat2.identity(imMat2);
imMat1[0] = imMat1[3] = bi.invMass;
imMat2[0] = imMat2[3] = bj.invMass;
/*
vec2.rotate(ri,this.xi,bi.angle);
vec2.rotate(rj,this.xj,bj.angle);
*/
// Add to linear velocity
vec2.scale(temp,vec2.transformMat2(temp,Gi,imMat1),deltalambda);
vec2.add( bi.vlambda, bi.vlambda, temp);
// This impulse is in the offset frame
// Also add contribution to angular
//bi.wlambda -= vec2.crossLength(temp,ri);
vec2.scale(temp,vec2.transformMat2(temp,Gj,imMat2),deltalambda);
vec2.add( bj.vlambda, bj.vlambda, temp);
//bj.wlambda -= vec2.crossLength(temp,rj);
// Add to angular velocity
bi.wlambda += bi.invInertia * G[2] * deltalambda;
bj.wlambda += bj.invInertia * G[5] * deltalambda;
};
/**
* Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps
* @method computeC
* @param {Number} eps
* @return {Number}
*/
Equation.prototype.computeC = function(eps){
return this.computeGiMGt() + eps;
};

View file

@ -1,22 +1,10 @@
var mat2 = require('../math/mat2')
, vec2 = require('../math/vec2')
, Equation = require('./Equation')
, Utils = require('../utils/Utils')
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
*
@ -31,28 +19,53 @@ 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}
*/
* 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}
*/
* 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}
*/
* 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;
/**
* A ContactEquation connected to this friction. The contact equation can be used to rescale the max force for the friction.
* @property contactEquation
* @type {ContactEquation}
*/
this.contactEquation = null;
/**
* The shape in body i that triggered this friction.
* @property shapeA
* @type {Shape}
*/
this.shapeA = null;
/**
* The shape in body j that triggered this friction.
* @property shapeB
* @type {Shape}
*/
this.shapeB = null;
/**
* The friction coefficient to use.
* @property frictionCoefficient
* @type {Number}
*/
this.frictionCoefficient = 0.3;
};
FrictionEquation.prototype = new Equation();
FrictionEquation.prototype.constructor = FrictionEquation;
@ -62,119 +75,34 @@ FrictionEquation.prototype.constructor = FrictionEquation;
* larger than this value.
* @method setSlipForce
* @param {Number} slipForce
* @deprecated Use .frictionCoefficient instead
*/
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,
var bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
t = this.t;
t = this.t,
G = this.G;
// 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];
// G = [-t -rixt t rjxt]
// And remember, this is a pure velocity constraint, g is always zero!
G[0] = -t[0];
G[1] = -t[1];
G[2] = -vec2.crossLength(ri,t);
G[3] = t[0];
G[4] = t[1];
G[5] = vec2.crossLength(rj,t);
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 GW = this.computeGW();
var GiMf = this.computeGiMf();
var B = /* - Gq * a */ - GW * b - h*GiMf;
var B = /* - g * 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;
};

View file

@ -0,0 +1,123 @@
var Constraint = require('./Constraint')
, vec2 = require('../math/vec2')
, Equation = require('./Equation')
module.exports = LockConstraint;
/**
* Locks the relative position between two bodies.
*
* @class LockConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {Object} [options]
* @param {Array} [options.localOffsetB] The offset of bodyB in bodyA's frame.
* @param {number} [options.localAngleB] The angle of bodyB in bodyA's frame.
* @param {number} [options.maxForce]
* @extends {Constraint}
*/
function LockConstraint(bodyA,bodyB,options){
Constraint.call(this,bodyA,bodyB);
var maxForce = ( typeof(options.maxForce)=="undefined" ? Number.MAX_VALUE : options.maxForce );
var localOffsetB = options.localOffsetB || vec2.fromValues(0,0);
localOffsetB = vec2.fromValues(localOffsetB[0],localOffsetB[1]);
var localAngleB = options.localAngleB || 0;
// Use 3 equations:
// gx = (xj - xi - l) * xhat = 0
// gy = (xj - xi - l) * yhat = 0
// gr = (xi - xj + r) * that = 0
//
// ...where:
// l is the localOffsetB vector rotated to world in bodyA frame
// r is the same vector but reversed and rotated from bodyB frame
// xhat, yhat are world axis vectors
// that is the tangent of r
//
// For the first two constraints, we get
// G*W = (vj - vi - ldot ) * xhat
// = (vj - vi - wi x l) * xhat
//
// Since (wi x l) * xhat = (l x xhat) * wi, we get
// G*W = [ -1 0 (-l x xhat) 1 0 0] * [vi wi vj wj]
//
// The last constraint gives
// GW = (vi - vj + wj x r) * that
// = [ that 0 -that (r x t) ]
var x = new Equation(bodyA,bodyB,-maxForce,maxForce),
y = new Equation(bodyA,bodyB,-maxForce,maxForce),
rot = new Equation(bodyA,bodyB,-maxForce,maxForce);
var l = vec2.create(),
g = vec2.create();
x.computeGq = function(){
vec2.rotate(l,localOffsetB,bodyA.angle);
vec2.sub(g,bodyB.position,bodyA.position);
vec2.sub(g,g,l);
return g[0];
}
y.computeGq = function(){
vec2.rotate(l,localOffsetB,bodyA.angle);
vec2.sub(g,bodyB.position,bodyA.position);
vec2.sub(g,g,l);
return g[1];
};
var r = vec2.create(),
t = vec2.create();
rot.computeGq = function(){
vec2.rotate(r,localOffsetB,bodyB.angle - localAngleB);
vec2.scale(r,r,-1);
vec2.sub(g,bodyA.position,bodyB.position);
vec2.add(g,g,r);
vec2.rotate(t,r,-Math.PI/2);
vec2.normalize(t,t);
return vec2.dot(g,t);
};
this.localOffsetB = localOffsetB;
this.localAngleB = localAngleB;
this.maxForce = maxForce;
var eqs = this.equations = [ x, y, rot ];
}
LockConstraint.prototype = new Constraint();
var l = vec2.create();
var r = vec2.create();
var t = vec2.create();
var xAxis = vec2.fromValues(1,0);
var yAxis = vec2.fromValues(0,1);
LockConstraint.prototype.update = function(){
var x = this.equations[0],
y = this.equations[1],
rot = this.equations[2],
bodyA = this.bodyA,
bodyB = this.bodyB;
vec2.rotate(l,this.localOffsetB,bodyA.angle);
vec2.rotate(r,this.localOffsetB,bodyB.angle - this.localAngleB);
vec2.scale(r,r,-1);
vec2.rotate(t,r,Math.PI/2);
vec2.normalize(t,t);
x.G[0] = -1;
x.G[1] = 0;
x.G[2] = -vec2.crossLength(l,xAxis);
x.G[3] = 1;
y.G[0] = 0;
y.G[1] = -1;
y.G[2] = -vec2.crossLength(l,yAxis);
y.G[4] = 1;
rot.G[0] = -t[0];
rot.G[1] = -t[1];
rot.G[3] = t[0];
rot.G[4] = t[1];
rot.G[5] = vec2.crossLength(r,t);
};

View file

@ -1,94 +0,0 @@
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;
};

View file

@ -1,58 +1,107 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, Equation = require('./Equation')
, vec2 = require('../math/vec2')
, RotationalLockEquation = require('./RotationalLockEquation')
module.exports = PrismaticConstraint;
/**
* Constraint that only allows translation along a line between the bodies, no rotation
* Constraint that only allows bodies to move along a line, relative to each other. See <a href="http://www.iforce2d.net/b2dtut/joints-prismatic">this tutorial</a>.
*
* @class PrismaticConstraint
* @constructor
* @extends {Constraint}
* @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}
* @param {Number} options.maxForce Max force to be applied by the constraint
* @param {Array} options.localAnchorA Body A's anchor point, defined in its own local frame.
* @param {Array} options.localAnchorB Body B's anchor point, defined in its own local frame.
* @param {Array} options.localAxisA An axis, defined in body A frame, that body B's anchor point may slide along.
*/
function PrismaticConstraint(bodyA,bodyB,options){
options = options || {};
Constraint.call(this,bodyA,bodyB);
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : 1e6;
// Get anchors
var localAnchorA = vec2.fromValues(0,0),
localAxisA = vec2.fromValues(1,0),
localAnchorB = vec2.fromValues(0,0);
if(options.localAnchorA) vec2.copy(localAnchorA, options.localAnchorA);
if(options.localAxisA) vec2.copy(localAxisA, options.localAxisA);
if(options.localAnchorB) vec2.copy(localAnchorB, options.localAnchorB);
// 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
];
/**
* @property localAnchorA
* @type {Array}
*/
this.localAnchorA = localAnchorA;
var tangentA = eqs[0],
tangentB = eqs[1];
/**
* @property localAnchorB
* @type {Array}
*/
this.localAnchorB = localAnchorB;
tangentA.minForce = tangentB.minForce = -maxForce;
tangentA.maxForce = tangentB.maxForce = maxForce;
/**
* @property localAxisA
* @type {Array}
*/
this.localAxisA = localAxisA;
var worldAxis = vec2.create();
if(options.worldAxis){
vec2.copy(worldAxis, options.worldAxis);
} else {
vec2.sub(worldAxis, bodyB.position, bodyA.position);
/*
The constraint violation for the common axis point is
g = ( xj + rj - xi - ri ) * t := gg*t
where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame.
gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t )
Note the use of the chain rule. Now we identify the jacobian
G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj]
The rotational part is just a rotation lock.
*/
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : Number.MAX_VALUE;
// Translational part
var trans = new Equation(bodyA,bodyB,-maxForce,maxForce);
var ri = new vec2.create(),
rj = new vec2.create(),
gg = new vec2.create(),
t = new vec2.create();
trans.computeGq = function(){
// g = ( xj + rj - xi - ri ) * t
return vec2.dot(gg,t);
};
trans.update = function(){
var G = this.G,
xi = bodyA.position,
xj = bodyB.position;
vec2.rotate(ri,localAnchorA,bodyA.angle);
vec2.rotate(rj,localAnchorB,bodyB.angle);
vec2.add(gg,xj,rj);
vec2.sub(gg,gg,xi);
vec2.sub(gg,gg,ri);
vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2);
G[0] = -t[0];
G[1] = -t[1];
G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg);
G[3] = t[0];
G[4] = t[1];
G[5] = vec2.crossLength(rj,t);
}
vec2.normalize(worldAxis,worldAxis);
var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce);
// 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);
this.equations.push(trans,rot);
}
PrismaticConstraint.prototype = new Constraint();
@ -62,22 +111,7 @@ PrismaticConstraint.prototype = new Constraint();
* @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);
var eqs = this.equations,
trans = eqs[0];
trans.update();
};

View file

@ -0,0 +1,208 @@
var Constraint = require('./Constraint')
, Equation = require('./Equation')
, RotationalVelocityEquation = require('./RotationalVelocityEquation')
, RotationalLockEquation = require('./RotationalLockEquation')
, vec2 = require('../math/vec2')
module.exports = RevoluteConstraint;
var worldPivotA = vec2.create(),
worldPivotB = vec2.create(),
xAxis = vec2.fromValues(1,0),
yAxis = vec2.fromValues(0,1),
g = vec2.create();
/**
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
* @class RevoluteConstraint
* @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 RevoluteConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
Constraint.call(this,bodyA,bodyB);
maxForce = typeof(maxForce)!="undefined" ? maxForce : Number.MAX_VALUE;
this.pivotA = pivotA;
this.pivotB = pivotB;
// Equations to be fed to the solver
var eqs = this.equations = [
new Equation(bodyA,bodyB,-maxForce,maxForce),
new Equation(bodyA,bodyB,-maxForce,maxForce),
];
var x = eqs[0];
var y = eqs[1];
x.computeGq = function(){
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
vec2.add(g, bodyB.position, worldPivotB);
vec2.sub(g, g, bodyA.position);
vec2.sub(g, g, worldPivotA);
return vec2.dot(g,xAxis);
};
y.computeGq = function(){
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
vec2.add(g, bodyB.position, worldPivotB);
vec2.sub(g, g, bodyA.position);
vec2.sub(g, g, worldPivotA);
return vec2.dot(g,yAxis);
};
y.minForce = x.minForce = -maxForce;
y.maxForce = x.maxForce = maxForce;
this.motorEquation = new RotationalVelocityEquation(bodyA,bodyB);
this.motorEnabled = false;
// Angle limits
this.lowerLimitEnabled = false;
this.upperLimitEnabled = false;
this.lowerLimit = 0;
this.upperLimit = 0;
this.upperLimitEquation = new RotationalLockEquation(bodyA,bodyB);
this.lowerLimitEquation = new RotationalLockEquation(bodyA,bodyB);
this.upperLimitEquation.minForce = 0;
this.lowerLimitEquation.maxForce = 0;
}
RevoluteConstraint.prototype = new Constraint();
RevoluteConstraint.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],
x = eqs[0],
y = eqs[1],
upperLimit = this.upperLimit,
lowerLimit = this.lowerLimit,
upperLimitEquation = this.upperLimitEquation,
lowerLimitEquation = this.lowerLimitEquation;
var relAngle = this.angle = bodyB.angle - bodyA.angle;
if(this.upperLimitEnabled && relAngle > upperLimit){
upperLimitEquation.angle = upperLimit;
if(eqs.indexOf(upperLimitEquation)==-1)
eqs.push(upperLimitEquation);
} else {
var idx = eqs.indexOf(upperLimitEquation);
if(idx != -1) eqs.splice(idx,1);
}
if(this.lowerLimitEnabled && relAngle < lowerLimit){
lowerLimitEquation.angle = lowerLimit;
if(eqs.indexOf(lowerLimitEquation)==-1)
eqs.push(lowerLimitEquation);
} else {
var idx = eqs.indexOf(lowerLimitEquation);
if(idx != -1) eqs.splice(idx,1);
}
/*
The constraint violation is
g = xj + rj - xi - ri
...where xi and xj are the body positions and ri and rj world-oriented offset vectors. Differentiate:
gdot = vj + wj x rj - vi - wi x ri
We split this into x and y directions. (let x and y be unit vectors along the respective axes)
gdot * x = ( vj + wj x rj - vi - wi x ri ) * x
= ( vj*x + (wj x rj)*x -vi*x -(wi x ri)*x
= ( vj*x + (rj x x)*wj -vi*x -(ri x x)*wi
= [ -x -(ri x x) x (rj x x)] * [vi wi vj wj]
= G*W
...and similar for y. We have then identified the jacobian entries for x and y directions:
Gx = [ x (rj x x) -x -(ri x x)]
Gy = [ y (rj x y) -y -(ri x y)]
*/
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
x.G[0] = -1;
x.G[1] = 0;
x.G[2] = -vec2.crossLength(worldPivotA,xAxis);
x.G[3] = 1;
x.G[4] = 0;
x.G[5] = vec2.crossLength(worldPivotB,xAxis);
y.G[0] = 0;
y.G[1] = -1;
y.G[2] = -vec2.crossLength(worldPivotA,yAxis);
y.G[3] = 0;
y.G[4] = 1;
y.G[5] = vec2.crossLength(worldPivotB,yAxis);
};
/**
* Enable the rotational motor
* @method enableMotor
*/
RevoluteConstraint.prototype.enableMotor = function(){
if(this.motorEnabled) return;
this.equations.push(this.motorEquation);
this.motorEnabled = true;
};
/**
* Disable the rotational motor
* @method disableMotor
*/
RevoluteConstraint.prototype.disableMotor = function(){
if(!this.motorEnabled) return;
var i = this.equations.indexOf(this.motorEquation);
this.equations.splice(i,1);
this.motorEnabled = false;
};
/**
* Check if the motor is enabled.
* @method motorIsEnabled
* @return {Boolean}
*/
RevoluteConstraint.prototype.motorIsEnabled = function(){
return !!this.motorEnabled;
};
/**
* Set the speed of the rotational constraint motor
* @method setMotorSpeed
* @param {Number} speed
*/
RevoluteConstraint.prototype.setMotorSpeed = function(speed){
if(!this.motorEnabled) return;
var i = this.equations.indexOf(this.motorEquation);
this.equations[i].relativeVelocity = speed;
};
/**
* Get the speed of the rotational constraint motor
* @method getMotorSpeed
* @return {Number} The current speed, or false if the motor is not enabled.
*/
RevoluteConstraint.prototype.getMotorSpeed = function(){
if(!this.motorEnabled) return false;
return this.motorEquation.relativeVelocity;
};

View file

@ -0,0 +1,37 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2');
module.exports = RotationalLockEquation;
/**
* Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter.
*
* @class RotationalLockEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
* @param {Object} options
* @param {Number} options.angle Angle to add to the local vector in body i.
*/
function RotationalLockEquation(bi,bj,options){
options = options || {};
Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
this.angle = options.angle || 0;
var G = this.G;
G[2] = 1;
G[5] = -1;
};
RotationalLockEquation.prototype = new Equation();
RotationalLockEquation.prototype.constructor = RotationalLockEquation;
var worldVectorA = vec2.create(),
worldVectorB = vec2.create(),
xAxis = vec2.fromValues(1,0),
yAxis = vec2.fromValues(0,1);
RotationalLockEquation.prototype.computeGq = function(){
vec2.rotate(worldVectorA,xAxis,this.bi.angle+this.angle);
vec2.rotate(worldVectorB,yAxis,this.bj.angle);
return vec2.dot(worldVectorA,worldVectorB);
};

View file

@ -13,58 +13,20 @@ module.exports = RotationalVelocityEquation;
* @param {Body} bj
*/
function RotationalVelocityEquation(bi,bj){
Equation.call(this,bi,bj,-1e6,1e6);
Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
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 G = this.G;
G[2] = -1;
G[5] = this.ratio;
var B = - Gq * a - GW * b - h*GiMf;
var GiMf = this.computeGiMf();
var GW = this.computeGW() + this.relativeVelocity;
var B = - 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;
};

View file

@ -11,12 +11,12 @@ EventEmitter.prototype = {
constructor: EventEmitter,
/**
* Add an event listener
* @method on
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
* Add an event listener
* @method on
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
on: function ( type, listener ) {
if ( this._listeners === undefined ) this._listeners = {};
var listeners = this._listeners;
@ -30,12 +30,12 @@ EventEmitter.prototype = {
},
/**
* Check if an event listener is added
* @method has
* @param {String} type
* @param {Function} listener
* @return {Boolean}
*/
* Check if an event listener is added
* @method has
* @param {String} type
* @param {Function} listener
* @return {Boolean}
*/
has: function ( type, listener ) {
if ( this._listeners === undefined ) return false;
var listeners = this._listeners;
@ -46,12 +46,12 @@ EventEmitter.prototype = {
},
/**
* Remove an event listener
* @method off
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
* Remove an event listener
* @method off
* @param {String} type
* @param {Function} listener
* @return {EventEmitter} The self object, for chainability.
*/
off: function ( type, listener ) {
if ( this._listeners === undefined ) return;
var listeners = this._listeners;
@ -63,12 +63,12 @@ EventEmitter.prototype = {
},
/**
* Emit an event.
* @method emit
* @param {Object} event
* @param {String} event.type
* @return {EventEmitter} The self object, for chainability.
*/
* Emit an event.
* @method emit
* @param {Object} event
* @param {String} event.type
* @return {EventEmitter} The self object, for chainability.
*/
emit: function ( event ) {
if ( this._listeners === undefined ) return;
var listeners = this._listeners;

View file

@ -17,65 +17,65 @@ function ContactMaterial(materialA, materialB, options){
options = options || {};
/**
* The contact material identifier
* @property id
* @type {Number}
*/
* The contact material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
/**
* First material participating in the contact material
* @property materialA
* @type {Material}
*/
* First material participating in the contact material
* @property materialA
* @type {Material}
*/
this.materialA = materialA;
/**
* Second material participating in the contact material
* @property materialB
* @type {Material}
*/
* Second material participating in the contact material
* @property materialB
* @type {Material}
*/
this.materialB = materialB;
/**
* Friction to use in the contact of these two materials
* @property friction
* @type {Number}
*/
* Friction to use in the contact of these two materials
* @property friction
* @type {Number}
*/
this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3;
/**
* Restitution to use in the contact of these two materials
* @property restitution
* @type {Number}
*/
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.3;
* Restitution to use in the contact of these two materials
* @property restitution
* @type {Number}
*/
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.0;
/**
* Stiffness of the resulting ContactEquation that this ContactMaterial generate
* @property stiffness
* @type {Number}
*/
* Stiffness of the resulting ContactEquation that this ContactMaterial generate
* @property stiffness
* @type {Number}
*/
this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : 1e7;
/**
* Relaxation of the resulting ContactEquation that this ContactMaterial generate
* @property relaxation
* @type {Number}
*/
* Relaxation of the resulting ContactEquation that this ContactMaterial generate
* @property relaxation
* @type {Number}
*/
this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3;
/**
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionStiffness
* @type {Number}
*/
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionStiffness
* @type {Number}
*/
this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : 1e7;
/**
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionRelaxation
* @type {Number}
*/
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate
* @property frictionRelaxation
* @type {Number}
*/
this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3;
};

View file

@ -11,9 +11,9 @@ var idCounter = 0;
*/
function Material(){
/**
* The material identifier
* @property id
* @type {Number}
*/
* The material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
};

View file

@ -194,7 +194,7 @@
i++;
}
}
if(iscs.length === 0) return [p.slice(0)];
if(iscs.length == 0) return [p.slice(0)];
var comp = function(u,v) {return PolyK._P.dist(a,u) - PolyK._P.dist(a,v); }
iscs.sort(comp);
@ -226,7 +226,7 @@
ps = PolyK._getPoints(ps, ind1, ind0);
i0.flag = i1.flag = false;
iscs.splice(0,2);
if(iscs.length === 0) pgs.push(ps);
if(iscs.length == 0) pgs.push(ps);
}
else { dir++; iscs.reverse(); }
if(dir>1) break;
@ -402,7 +402,7 @@
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
if (Den === 0) return null; // parallel
if (Den == 0) return null; // parallel
var A = (a1.x * a2.y - a1.y * a2.x);
var B = (b1.x * b2.y - b1.y * b2.x);
@ -424,7 +424,7 @@
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
if (Den === 0) return null; // parallel
if (Den == 0) return null; // parallel
var A = (a1.x * a2.y - a1.y * a2.x);
var B = (b1.x * b2.y - b1.y * b2.x);
@ -472,6 +472,6 @@
PolyK._tp = [];
for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0));
*/
*/
module.exports = PolyK;

View file

@ -1,4 +1,6 @@
var vec2 = require('../math/vec2');
var vec2 = require('../math/vec2')
, decomp = require('poly-decomp')
, Convex = require('../shapes/Convex')
module.exports = Body;
@ -25,156 +27,176 @@ function Body(options){
options = options || {};
/**
* The body identifyer
* @property id
* @type {Number}
*/
* The body identifyer
* @property id
* @type {Number}
*/
this.id = ++Body._idCounter;
/**
* The shapes of the body. The local transform of the shape in .shapes[i] is
* defined by .shapeOffsets[i] and .shapeAngles[i].
*
* @property shapes
* @type {Array}
*/
* The shapes of the body. The local transform of the shape in .shapes[i] is
* defined by .shapeOffsets[i] and .shapeAngles[i].
*
* @property shapes
* @type {Array}
*/
this.shapes = [];
/**
* The local shape offsets, relative to the body center of mass. This is an
* array of Float32Array.
* @property shapeOffsets
* @type {Array}
*/
* The local shape offsets, relative to the body center of mass. This is an
* array of Float32Array.
* @property shapeOffsets
* @type {Array}
*/
this.shapeOffsets = [];
/**
* The body-local shape angle transforms. This is an array of numbers (angles).
* @property shapeAngles
* @type {Array}
*/
* The body-local shape angle transforms. This is an array of numbers (angles).
* @property shapeAngles
* @type {Array}
*/
this.shapeAngles = [];
/**
* The mass of the body.
* @property mass
* @type {number}
*/
* The mass of the body.
* @property mass
* @type {number}
*/
this.mass = options.mass || 0;
/**
* The inverse mass of the body.
* @property invMass
* @type {number}
*/
* The inverse mass of the body.
* @property invMass
* @type {number}
*/
this.invMass = 0;
/**
* The inertia of the body around the Z axis.
* @property inertia
* @type {number}
*/
* The inertia of the body around the Z axis.
* @property inertia
* @type {number}
*/
this.inertia = 0;
/**
* The inverse inertia of the body.
* @property invInertia
* @type {number}
*/
* The inverse inertia of the body.
* @property invInertia
* @type {number}
*/
this.invInertia = 0;
this.updateMassProperties();
/**
* The position of the body
* @property position
* @type {Float32Array}
*/
* The position of the body
* @property position
* @type {Float32Array}
*/
this.position = vec2.fromValues(0,0);
if(options.position) vec2.copy(this.position, options.position);
/**
* The velocity of the body
* @property velocity
* @type {Float32Array}
*/
* The velocity of the body
* @property velocity
* @type {Float32Array}
*/
this.velocity = vec2.fromValues(0,0);
if(options.velocity) vec2.copy(this.velocity, options.velocity);
/**
* Constraint velocity that was added to the body during the last step.
* @property vlambda
* @type {Float32Array}
*/
* Constraint velocity that was added to the body during the last step.
* @property vlambda
* @type {Float32Array}
*/
this.vlambda = vec2.fromValues(0,0);
/**
* Angular constraint velocity that was added to the body during last step.
* @property wlambda
* @type {Float32Array}
*/
* Angular constraint velocity that was added to the body during last step.
* @property wlambda
* @type {Float32Array}
*/
this.wlambda = 0;
/**
* The angle of the body
* @property angle
* @type {number}
*/
* The angle of the body
* @property angle
* @type {number}
*/
this.angle = options.angle || 0;
/**
* The angular velocity of the body
* @property angularVelocity
* @type {number}
*/
* The angular velocity of the body
* @property angularVelocity
* @type {number}
*/
this.angularVelocity = options.angularVelocity || 0;
/**
* The force acting on the body
* @property force
* @type {Float32Array}
*/
* The force acting on the body
* @property force
* @type {Float32Array}
*/
this.force = vec2.create();
if(options.force) vec2.copy(this.force, options.force);
/**
* The angular force acting on the body
* @property angularForce
* @type {number}
*/
* The angular force acting on the body
* @property angularForce
* @type {number}
*/
this.angularForce = options.angularForce || 0;
/**
* The type of motion this body has. Should be one of: Body.STATIC (the body
* does not move), Body.DYNAMIC (body can move and respond to collisions)
* and Body.KINEMATIC (only moves according to its .velocity).
*
* @property motionState
* @type {number}
*
* @example
* // This body will move and interact with other bodies
* var dynamicBody = new Body();
* dynamicBody.motionState = Body.DYNAMIC;
*
* @example
* // This body will not move at all
* var staticBody = new Body();
* staticBody.motionState = Body.STATIC;
*
* @example
* // This body will only move if you change its velocity
* var kinematicBody = new Body();
* kinematicBody.motionState = Body.KINEMATIC;
*/
this.motionState = this.mass === 0 ? Body.STATIC : Body.DYNAMIC;
* The linear damping acting on the body in the velocity direction
* @property damping
* @type {Number}
*/
this.damping = typeof(options.damping)=="number" ? options.damping : 0.1;
/**
* Bounding circle radius
* @property boundingRadius
* @type {Number}
*/
* The angular force acting on the body
* @property angularDamping
* @type {Number}
*/
this.angularDamping = typeof(options.angularDamping)=="number" ? options.angularDamping : 0.1;
/**
* The type of motion this body has. Should be one of: Body.STATIC (the body
* does not move), Body.DYNAMIC (body can move and respond to collisions)
* and Body.KINEMATIC (only moves according to its .velocity).
*
* @property motionState
* @type {number}
*
* @example
* // This body will move and interact with other bodies
* var dynamicBody = new Body();
* dynamicBody.motionState = Body.DYNAMIC;
*
* @example
* // This body will not move at all
* var staticBody = new Body();
* staticBody.motionState = Body.STATIC;
*
* @example
* // This body will only move if you change its velocity
* var kinematicBody = new Body();
* kinematicBody.motionState = Body.KINEMATIC;
*/
this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC;
/**
* Bounding circle radius
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
this.concavePath = null;
this.lastDampingScale = 1;
this.lastAngularDampingScale = 1;
this.lastDampingTimeStep = -1;
};
Body._idCounter = 0;
@ -225,6 +247,15 @@ Body.prototype.updateBoundingRadius = function(){
* body.addShape(shape,[0,1],Math.PI/2);
*/
Body.prototype.addShape = function(shape,offset,angle){
angle = angle || 0.0;
// Copy the offset vector
if(offset){
offset = vec2.fromValues(offset[0],offset[1]);
} else {
offset = vec2.fromValues(0,0);
}
this.shapes .push(shape);
this.shapeOffsets.push(offset);
this.shapeAngles .push(angle);
@ -232,6 +263,24 @@ Body.prototype.addShape = function(shape,offset,angle){
this.updateBoundingRadius();
};
/**
* Remove a shape
* @method removeShape
* @param {Shape} shape
* @return {Boolean} True if the shape was found and removed, else false.
*/
Body.prototype.removeShape = function(shape){
var idx = this.shapes.indexOf(shape);
if(idx != -1){
this.shapes.splice(idx,1);
this.shapeOffsets.splice(idx,1);
this.shapeAngles.splice(idx,1);
return true;
} else
return false;
};
/**
* Updates .inertia, .invMass, .invInertia for this Body. Should be called when
* changing the structure or mass of the Body.
@ -305,6 +354,175 @@ Body.prototype.toWorldFrame = function(out, localPoint){
vec2.toGlobalFrame(out, localPoint, this.position, this.angle);
};
/**
* Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points.
* @method fromPolygon
* @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes.
* @param {Object} [options]
* @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
* @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself.
* @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
* @return {Boolean} True on success, else false.
*/
Body.prototype.fromPolygon = function(path,options){
options = options || {};
// Remove all shapes
for(var i=this.shapes.length; i>=0; --i)
this.removeShape(this.shapes[i]);
var p = new decomp.Polygon();
p.vertices = path;
// Make it counter-clockwise
p.makeCCW();
if(typeof(options.removeCollinearPoints)=="number"){
p.removeCollinearPoints(options.removeCollinearPoints);
}
// Check if any line segment intersects the path itself
if(typeof(options.skipSimpleCheck) == "undefined"){
if(!p.isSimple()) return false;
}
// Save this path for later
this.concavePath = p.vertices.slice(0);
for(var i=0; i<this.concavePath.length; i++){
var v = [0,0];
vec2.copy(v,this.concavePath[i]);
this.concavePath[i] = v;
}
// Slow or fast decomp?
var convexes;
if(options.optimalDecomp) convexes = p.decomp();
else convexes = p.quickDecomp();
var cm = vec2.create();
// Add convexes
for(var i=0; i!==convexes.length; i++){
// Create convex
var c = new Convex(convexes[i].vertices);
// Move all vertices so its center of mass is in the local center of the convex
for(var j=0; j!==c.vertices.length; j++){
var v = c.vertices[j];
vec2.sub(v,v,c.centerOfMass);
}
vec2.scale(cm,c.centerOfMass,1);
c.updateTriangles();
c.updateCenterOfMass();
c.updateBoundingRadius();
// Add the shape
this.addShape(c,cm);
}
this.adjustCenterOfMass();
return true;
};
var adjustCenterOfMass_tmp1 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp2 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp3 = vec2.fromValues(0,0),
adjustCenterOfMass_tmp4 = vec2.fromValues(0,0);
/**
* Moves the shape offsets so their center of mass becomes the body center of mass.
* @method adjustCenterOfMass
*/
Body.prototype.adjustCenterOfMass = function(){
var zero = adjustCenterOfMass_tmp1,
offset_times_area = adjustCenterOfMass_tmp2,
sum = adjustCenterOfMass_tmp3,
cm = adjustCenterOfMass_tmp4,
totalArea = 0;
vec2.set(sum,0,0);
vec2.set(zero,0,0);
for(var i=0; i!==this.shapes.length; i++){
var s = this.shapes[i],
offset = this.shapeOffsets[i] || zero;
vec2.scale(offset_times_area,offset,s.area);
vec2.add(sum,sum,offset_times_area);
totalArea += s.area;
}
vec2.scale(cm,sum,1/totalArea);
// Now move all shapes
for(var i=0; i!==this.shapes.length; i++){
var s = this.shapes[i],
offset = this.shapeOffsets[i];
// Offset may be undefined. Fix that.
if(!offset){
offset = this.shapeOffsets[i] = vec2.create();
}
vec2.sub(offset,offset,cm);
}
// Move the body position too
vec2.add(this.position,this.position,cm);
// And concave path
for(var i=0; this.concavePath && i<this.concavePath.length; i++){
vec2.sub(this.concavePath[i], this.concavePath[i], cm);
}
this.updateMassProperties();
this.updateBoundingRadius();
};
/**
* Sets the force on the body to zero.
* @method setZeroForce
*/
Body.prototype.setZeroForce = function(){
vec2.set(this.force,0.0,0.0);
this.angularForce = 0.0;
};
Body.prototype.resetConstraintVelocity = function(){
var b = this,
vlambda = b.vlambda;
vec2.set(vlambda,0,0);
b.wlambda = 0;
};
Body.prototype.addConstraintVelocity = function(){
var b = this,
v = b.velocity;
vec2.add( v, v, b.vlambda);
b.angularVelocity += b.wlambda;
};
/**
* Apply damping, see <a href="http://code.google.com/p/bullet/issues/detail?id=74">this</a> for details.
* @method applyDamping
* @param {number} dt Current time step
*/
Body.prototype.applyDamping = function(dt){
if(this.motionState & Body.DYNAMIC){ // Only for dynamic bodies
// Since Math.pow generates garbage we check if we can reuse the scaling coefficient from last step
if(dt != this.lastDampingTimeStep){
this.lastDampingScale = Math.pow(1.0 - this.damping,dt);
this.lastAngularDampingScale = Math.pow(1.0 - this.angularDamping,dt);
this.lastDampingTimeStep = dt;
}
var v = this.velocity;
vec2.scale(v,v,this.lastDampingScale);
this.angularVelocity *= this.lastAngularDampingScale;
}
};
/**
* Dynamic body.
* @property DYNAMIC

View file

@ -22,52 +22,52 @@ function Spring(bodyA,bodyB,options){
options = options || {};
/**
* Rest length of the spring.
* @property restLength
* @type {number}
*/
* Rest length of the spring.
* @property restLength
* @type {number}
*/
this.restLength = typeof(options.restLength)=="number" ? options.restLength : 1;
/**
* Stiffness of the spring.
* @property stiffness
* @type {number}
*/
* Stiffness of the spring.
* @property stiffness
* @type {number}
*/
this.stiffness = options.stiffness || 100;
/**
* Damping of the spring.
* @property damping
* @type {number}
*/
* Damping of the spring.
* @property damping
* @type {number}
*/
this.damping = options.damping || 1;
/**
* First connected body.
* @property bodyA
* @type {Body}
*/
* First connected body.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second connected body.
* @property bodyB
* @type {Body}
*/
* Second connected body.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
/**
* Anchor for bodyA in local bodyA coordinates.
* @property localAnchorA
* @type {Array}
*/
* Anchor for bodyA in local bodyA coordinates.
* @property localAnchorA
* @type {Array}
*/
this.localAnchorA = vec2.fromValues(0,0);
/**
* Anchor for bodyB in local bodyB coordinates.
* @property localAnchorB
* @type {Array}
*/
* Anchor for bodyB in local bodyB coordinates.
* @property localAnchorB
* @type {Array}
*/
this.localAnchorB = vec2.fromValues(0,0);
if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA);

View file

@ -17,11 +17,12 @@ module.exports = {
Island : require('./solver/IslandSolver'),
IslandSolver : require('./solver/IslandSolver'),
Line : require('./shapes/Line'),
LockConstraint : require('./constraints/LockConstraint'),
Material : require('./material/Material'),
NaiveBroadphase : require('./collision/NaiveBroadphase'),
Particle : require('./shapes/Particle'),
Plane : require('./shapes/Plane'),
PointToPointConstraint : require('./constraints/PointToPointConstraint'),
RevoluteConstraint : require('./constraints/RevoluteConstraint'),
PrismaticConstraint : require('./constraints/PrismaticConstraint'),
Rectangle : require('./shapes/Rectangle'),
RotationalVelocityEquation : require('./constraints/RotationalVelocityEquation'),

View file

@ -0,0 +1,146 @@
var World = require('../world/World')
var num = { type:"number", required:true };
/*
* Serialize a World instance to JSON
* @method serialize
* @param {World} world
* @return {Object}
*/
exports.serialize = function(world){
return {};
};
/*
* Load a World instance from JSON
* @param {Object} json
* @return {World}
*/
exports.deserialize = function(json){
var world = new World();
return world;
};
var schemas = exports.schemas = {};
schemas['0.3.0'] = {
type: "object",
additionalProperties:false,
properties: {
gravity: { $ref:"vec2" },
p2: { type:"string", pattern:"^0.3$", required:true },
solver: { type:"object", required:true },
broadphase: { type:"object", required:true },
bodies: {
type:"array",
required:true,
additionalItems:false,
items:{
type:"object",
additionalProperties:false,
properties:{
id : num,
mass : num,
angle : num,
position : { $ref:"vec2" },
velocity : { $ref:"vec2" },
angularVelocity : num,
force : { $ref:"vec2" },
shapes : { required:true, type:"array" },
concavePath : { required:true, type:["array","null"] },
},
}
},
springs: {
type:"array",
required:true,
additionalItems:false,
items:{
type:"object",
additionalProperties:false,
properties:{
bodyA : num,
bodyB : num,
stiffness : num,
damping : num,
restLength : num,
localAnchorA : { $ref:"vec2" },
localAnchorB : { $ref:"vec2" },
},
},
},
constraints: {
type:"array",
required:true,
items:[{
type:"object",
additionalProperties:false,
properties:{
bodyA: num,
bodyB: num,
type: { type:"string", match:"^DistanceConstraint$" },
distance: num,
maxForce: num,
},
},{
type:"object",
additionalProperties:false,
properties:{
bodyA: num,
bodyB: num,
type: { type:"string", match:"^PrismaticConstraint$" },
localAxisA: { $ref:"vec2" },
localAxisB: { $ref:"vec2" },
maxForce: num,
},
},{
type:"object",
additionalProperties:false,
properties:{
bodyA: num,
bodyB: num,
type: { type:"string", match:'^RevoluteConstraint$' },
pivotA: { $ref:"vec2" },
pivotB: { $ref:"vec2" },
maxForce: num,
motorSpeed: { type:["number","boolean"] },
lowerLimit: num,
lowerLimitEnabled: { type:"boolean" },
upperLimit: num,
upperLimitEnabled: { type:"boolean" },
},
}],
},
contactMaterials: {
type:"array",
required:true,
additionalItems:false,
items: {
properties : {
id: num,
materialA: num,
materialB: num,
friction: num,
restitution: num,
stiffness: num,
relaxation: num,
frictionStiffness: num,
frictionRelaxation: num,
}
}
},
}
};
exports.vec2 = {
id: "/vec2",
type:"array",
maxItems:2,
minItems:2,
items:{
type:"number",
},
additionalItems:false,
required:true,
};

View file

@ -7,15 +7,15 @@ module.exports = Circle;
* @class Circle
* @extends {Shape}
* @constructor
* @param {number} radius
* @param {number} radius The radius of this circle
*/
function Circle(radius){
/**
* The radius of the circle.
* @property radius
* @type {number}
*/
* The radius of the circle.
* @property radius
* @type {number}
*/
this.radius = radius || 1;
Shape.call(this,Shape.CIRCLE);

View file

@ -1,6 +1,7 @@
var Shape = require('./Shape')
, vec2 = require('../math/vec2')
, polyk = require('../math/polyk')
, decomp = require('poly-decomp')
module.exports = Convex;
@ -14,24 +15,31 @@ module.exports = Convex;
function Convex(vertices){
/**
* Vertices defined in the local frame.
* @property vertices
* @type {Array}
*/
* Vertices defined in the local frame.
* @property vertices
* @type {Array}
*/
this.vertices = vertices || [];
// Copy the verts
for(var i=0; i<this.vertices.length; i++){
var v = vec2.fromValues();
vec2.copy(v,this.vertices[i]);
this.vertices[i] = v;
}
/**
* The center of mass of the Convex
* @property centerOfMass
* @type {Float32Array}
*/
* The center of mass of the Convex
* @property centerOfMass
* @type {Float32Array}
*/
this.centerOfMass = vec2.fromValues(0,0);
/**
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
* @property triangles
* @type {Array}
*/
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
* @property triangles
* @type {Array}
*/
this.triangles = [];
if(this.vertices.length){
@ -40,10 +48,10 @@ function Convex(vertices){
}
/**
* The bounding radius of the convex
* @property boundingRadius
* @type {Number}
*/
* The bounding radius of the convex
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
this.updateBoundingRadius();
@ -51,6 +59,10 @@ function Convex(vertices){
};
Convex.prototype = new Shape();
/**
* Update the .triangles property
* @method updateTriangles
*/
Convex.prototype.updateTriangles = function(){
this.triangles.length = 0;
@ -85,6 +97,11 @@ var updateCenterOfMass_centroid = vec2.create(),
updateCenterOfMass_ca = vec2.create(),
updateCenterOfMass_cb = vec2.create(),
updateCenterOfMass_n = vec2.create();
/**
* Update the .centerOfMass property.
* @method updateCenterOfMass
*/
Convex.prototype.updateCenterOfMass = function(){
var triangles = this.triangles,
verts = this.vertices,
@ -100,8 +117,9 @@ Convex.prototype.updateCenterOfMass = function(){
centroid_times_mass = updateCenterOfMass_centroid_times_mass;
vec2.set(cm,0,0);
var totalArea = 0;
for(var i=0; i<triangles.length; i++){
for(var i=0; i!==triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
@ -109,17 +127,17 @@ Convex.prototype.updateCenterOfMass = function(){
vec2.centroid(centroid,a,b,c);
vec2.sub(ca, c, a);
vec2.sub(cb, c, b);
// Get mass for the triangle (density=1 in this case)
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
var m = 0.5 * vec2.crossLength(ca,cb);
var m = decomp.Point.area(a,b,c)
totalArea += m;
// Add to center of mass
vec2.scale(centroid_times_mass, centroid, m);
vec2.add(cm, cm, centroid_times_mass);
}
vec2.scale(cm,cm,1/totalArea);
};
/**
@ -148,7 +166,8 @@ Convex.prototype.computeMomentOfInertia = function(mass){
// Get total convex area and density
var area = polyk.GetArea(polykVerts);
var density = mass / area;
this.updateArea();
var density = mass / this.area;
// Temp vectors
var a = vec2.create(),
@ -177,15 +196,15 @@ Convex.prototype.computeMomentOfInertia = function(mass){
vec2.sub(ca, c, a);
vec2.sub(cb, c, b);
var area_triangle = 0.5 * vec2.crossLength(ca,cb);
var area_triangle = decomp.Point.area(a,b,c)
var base = vec2.length(ca);
var height = 2*area_triangle / base; // a=b*h/2 => h=2*a/b
// Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
var I_triangle = (base * (Math.pow(height,3))) / 36;
// Get mass for the triangle
var m = base*height/2 * density;
var m = area_triangle * density;
// Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
var I_triangle = m*(base * (Math.pow(height,3))) / 36;
// Add to total inertia using parallel axis theorem
var r2 = vec2.squaredLength(centroid);
@ -211,3 +230,26 @@ Convex.prototype.updateBoundingRadius = function(){
this.boundingRadius = Math.sqrt(r2);
};
/**
* Update the .area
* @method updateArea
*/
Convex.prototype.updateArea = function(){
this.updateTriangles();
this.area = 0;
var triangles = this.triangles,
verts = this.vertices;
for(var i=0; i!==triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
c = verts[t[2]];
// Get mass for the triangle (density=1 in this case)
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
var m = decomp.Point.area(a,b,c)
this.area += m;
}
};

View file

@ -5,16 +5,17 @@ module.exports = Line;
/**
* Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0].
* @class Line
* @param {Number} length The total length of the line
* @extends {Shape}
* @constructor
*/
function Line(length){
/**
* Length of this line
* @property length
* @type {Number}
*/
* Length of this line
* @property length
* @type {Number}
*/
this.length = length;
Shape.call(this,Shape.LINE);

View file

@ -12,10 +12,19 @@ function Plane(){
Shape.call(this,Shape.PLANE);
};
Plane.prototype = new Shape();
/**
* Compute moment of inertia
* @method computeMomentOfInertia
*/
Plane.prototype.computeMomentOfInertia = function(mass){
return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here
};
/**
* Update the bounding radius
* @method updateBoundingRadius
*/
Plane.prototype.updateBoundingRadius = function(){
this.boundingRadius = Number.MAX_VALUE;
};

View file

@ -8,6 +8,8 @@ module.exports = Rectangle;
* Rectangle shape class.
* @class Rectangle
* @constructor
* @param {Number} w Width
* @param {Number} h Height
* @extends {Convex}
*/
function Rectangle(w,h){
@ -16,7 +18,18 @@ function Rectangle(w,h){
vec2.fromValues( w/2, h/2),
vec2.fromValues(-w/2, h/2)];
/**
* Total width of the rectangle
* @property width
* @type {Number}
*/
this.width = w;
/**
* Total height of the rectangle
* @property height
* @type {Number}
*/
this.height = h;
Convex.call(this,verts);
@ -35,6 +48,10 @@ Rectangle.prototype.computeMomentOfInertia = function(mass){
return mass * (h*h + w*w) / 12;
};
/**
* Update the bounding radius
* @method updateBoundingRadius
*/
Rectangle.prototype.updateBoundingRadius = function(){
var w = this.width,
h = this.height;

View file

@ -9,57 +9,66 @@ function Shape(type){
this.type = type;
/**
* Bounding circle radius of this shape
* @property boundingRadius
* @type {Number}
*/
* Bounding circle radius of this shape
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
/**
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>.
* @property collisionGroup
* @type {Number}
* @example
* // Setup bits for each available group
* var PLAYER = Math.pow(2,0),
* ENEMY = Math.pow(2,1),
* GROUND = Math.pow(2,2)
*
* // Put shapes into their groups
* player1Shape.collisionGroup = PLAYER;
* player2Shape.collisionGroup = PLAYER;
* enemyShape .collisionGroup = ENEMY;
* groundShape .collisionGroup = GROUND;
*
* // Assign groups that each shape collide with.
* // Note that the players can collide with ground and enemies, but not with other players.
* player1Shape.collisionMask = ENEMY | GROUND;
* player2Shape.collisionMask = ENEMY | GROUND;
* enemyShape .collisionMask = PLAYER | GROUND;
* groundShape .collisionMask = PLAYER | ENEMY;
*
* @example
* // How collision check is done
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
* // The shapes will collide
* }
*/
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>.
* @property collisionGroup
* @type {Number}
* @example
* // Setup bits for each available group
* var PLAYER = Math.pow(2,0),
* ENEMY = Math.pow(2,1),
* GROUND = Math.pow(2,2)
*
* // Put shapes into their groups
* player1Shape.collisionGroup = PLAYER;
* player2Shape.collisionGroup = PLAYER;
* enemyShape .collisionGroup = ENEMY;
* groundShape .collisionGroup = GROUND;
*
* // Assign groups that each shape collide with.
* // Note that the players can collide with ground and enemies, but not with other players.
* player1Shape.collisionMask = ENEMY | GROUND;
* player2Shape.collisionMask = ENEMY | GROUND;
* enemyShape .collisionMask = PLAYER | GROUND;
* groundShape .collisionMask = PLAYER | ENEMY;
*
* @example
* // How collision check is done
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
* // The shapes will collide
* }
*/
this.collisionGroup = 1;
/**
* Collision mask of this shape. See .collisionGroup.
* @property collisionMask
* @type {Number}
*/
* Collision mask of this shape. See .collisionGroup.
* @property collisionMask
* @type {Number}
*/
this.collisionMask = 1;
if(type) this.updateBoundingRadius();
/**
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
* @property material
* @type {Material}
*/
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
* @property material
* @type {Material}
*/
this.material = null;
/**
* Area of this shape.
* @property area
* @type {Number}
*/
this.area = 0;
this.updateArea();
};
Shape.CIRCLE = 1;
@ -88,3 +97,11 @@ Shape.prototype.computeMomentOfInertia = function(mass){
Shape.prototype.updateBoundingRadius = function(){
throw new Error("Shape.updateBoundingRadius is not implemented in this Shape...");
};
/**
* Update the .area property of the shape.
* @method updateArea
*/
Shape.prototype.updateArea = function(){
// To be implemented in all subclasses
};

View file

@ -1,10 +1,10 @@
var vec2 = require('../math/vec2'),
Solver = require('./Solver');
var vec2 = require('../math/vec2')
, Solver = require('./Solver')
, Utils = require('../utils/Utils')
, FrictionEquation = require('../constraints/FrictionEquation')
module.exports = GSSolver;
var ARRAY_TYPE = Float32Array || Array;
/**
* Iterative Gauss-Seidel constraint equation solver.
*
@ -19,59 +19,66 @@ var ARRAY_TYPE = Float32Array || Array;
* @param {Number} options.tolerance
*/
function GSSolver(options){
Solver.call(this);
Solver.call(this,options);
options = options || {};
this.iterations = options.iterations || 10;
this.tolerance = options.tolerance || 0;
this.debug = options.debug || false;
this.arrayStep = 30;
this.lambda = new ARRAY_TYPE(this.arrayStep);
this.Bs = new ARRAY_TYPE(this.arrayStep);
this.invCs = new ARRAY_TYPE(this.arrayStep);
/**
* Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
* @type {Boolean}
* @property useGlobalEquationParameters
*/
* The number of iterations to do when solving. More gives better results, but is more expensive.
* @property iterations
* @type {Number}
*/
this.iterations = options.iterations || 10;
/**
* The error tolerance. If the total error is below this limit, the solver will stop. Set to zero for as good solution as possible.
* @property tolerance
* @type {Number}
*/
this.tolerance = options.tolerance || 0;
this.debug = options.debug || false;
this.arrayStep = 30;
this.lambda = new Utils.ARRAY_TYPE(this.arrayStep);
this.Bs = new Utils.ARRAY_TYPE(this.arrayStep);
this.invCs = new Utils.ARRAY_TYPE(this.arrayStep);
/**
* Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
* @type {Boolean}
* @property useGlobalEquationParameters
*/
this.useGlobalEquationParameters = true;
/**
* Global equation stiffness.
* @property stiffness
* @type {Number}
*/
* Global equation stiffness. Larger number gives harder contacts, etc, but may also be more expensive to compute, or it will make your simulation explode.
* @property stiffness
* @type {Number}
*/
this.stiffness = 1e6;
/**
* Global equation relaxation.
* @property relaxation
* @type {Number}
*/
* Global equation relaxation. This is the number of timesteps required for a constraint to be resolved. Larger number will give softer contacts. Set to around 3 or 4 for good enough results.
* @property relaxation
* @type {Number}
*/
this.relaxation = 4;
/**
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
* @property useZeroRHS
* @type {Boolean}
*/
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
* @property useZeroRHS
* @type {Boolean}
*/
this.useZeroRHS = false;
/**
* Number of friction iterations to skip. If .skipFrictionIterations=2, then no FrictionEquations will be iterated until the third iteration.
* @property skipFrictionIterations
* @type {Number}
*/
this.skipFrictionIterations = 2;
};
GSSolver.prototype = new Solver();
/**
* Set stiffness parameters
*
* @method setSpookParams
* @param {number} k
* @param {number} d
* @deprecated
*/
GSSolver.prototype.setSpookParams = function(k,d){
this.stiffness = k;
this.relaxation = d;
};
/**
* Solve the system of equations
* @method solve
@ -79,8 +86,12 @@ GSSolver.prototype.setSpookParams = function(k,d){
* @param {World} world World to solve
*/
GSSolver.prototype.solve = function(dt,world){
this.sortEquations();
var iter = 0,
maxIter = this.iterations,
skipFrictionIter = this.skipFrictionIterations,
tolSquared = this.tolerance*this.tolerance,
equations = this.equations,
Neq = equations.length,
@ -99,9 +110,9 @@ GSSolver.prototype.solve = function(dt,world){
// Things that does not change during iteration can be computed once
if(this.lambda.length < Neq){
this.lambda = new ARRAY_TYPE(Neq + this.arrayStep);
this.Bs = new ARRAY_TYPE(Neq + this.arrayStep);
this.invCs = new ARRAY_TYPE(Neq + this.arrayStep);
this.lambda = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
this.Bs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
this.invCs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
}
var invCs = this.invCs,
Bs = this.Bs,
@ -130,9 +141,7 @@ GSSolver.prototype.solve = function(dt,world){
// Reset vlambda
for(i=0; i!==Nbodies; i++){
var b=bodies[i], vlambda=b.vlambda;
set(vlambda,0,0);
b.wlambda = 0;
bodies[i].resetConstraintVelocity();
}
// Iterate over equations
@ -142,49 +151,59 @@ GSSolver.prototype.solve = function(dt,world){
deltalambdaTot = 0.0;
for(j=0; j!==Neq; j++){
c = equations[j];
if(c instanceof FrictionEquation && iter < skipFrictionIter)
continue;
var _eps = useGlobalParams ? eps : c.eps;
// Compute iteration
maxForce = c.maxForce;
minForce = c.minForce;
B = Bs[j];
invC = invCs[j];
lambdaj = lambda[j];
GWlambda = c.computeGWlambda(_eps);
if(useZeroRHS) B = 0;
deltalambda = invC * ( B - GWlambda - _eps * lambdaj );
// Clamp if we are not within the min/max interval
lambdaj_plus_deltalambda = lambdaj + deltalambda;
if(lambdaj_plus_deltalambda < minForce){
deltalambda = minForce - lambdaj;
} else if(lambdaj_plus_deltalambda > maxForce){
deltalambda = maxForce - lambdaj;
}
lambda[j] += deltalambda;
deltalambdaTot += Math.abs(deltalambda);
c.addToWlambda(deltalambda);
var deltalambda = GSSolver.iterateEquation(j,c,_eps,Bs,invCs,lambda,useZeroRHS,dt);
if(tolSquared !== 0) deltalambdaTot += Math.abs(deltalambda);
}
// If the total error is small enough - stop iterate
if(deltalambdaTot*deltalambdaTot <= tolSquared) break;
if(tolSquared !== 0 && deltalambdaTot*deltalambdaTot <= tolSquared) break;
}
// Add result to velocity
for(i=0; i!==Nbodies; i++){
var b=bodies[i], v=b.velocity;
add( v, v, b.vlambda);
b.angularVelocity += b.wlambda;
bodies[i].addConstraintVelocity();
}
}
errorTot = deltalambdaTot;
};
GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt){
// Compute iteration
var B = Bs[j],
invC = invCs[j],
lambdaj = lambda[j],
GWlambda = eq.computeGWlambda(eps);
if(eq instanceof FrictionEquation){
// Rescale the max friction force according to the normal force
eq.maxForce = eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
eq.minForce = -eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
}
var maxForce = eq.maxForce,
minForce = eq.minForce;
if(useZeroRHS) B = 0;
var deltalambda = invC * ( B - GWlambda - eps * lambdaj );
// Clamp if we are not within the min/max interval
var lambdaj_plus_deltalambda = lambdaj + deltalambda;
if(lambdaj_plus_deltalambda < minForce){
deltalambda = minForce - lambdaj;
} else if(lambdaj_plus_deltalambda > maxForce){
deltalambda = maxForce - lambdaj;
}
lambda[j] += deltalambda;
eq.multiplier = lambda[j] / dt;
eq.addToWlambda(deltalambda);
return deltalambda;
};

View file

@ -8,17 +8,17 @@ module.exports = Island;
function Island(){
/**
* Current equations in this island.
* @property equations
* @type {Array}
*/
* Current equations in this island.
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* Current bodies in this island.
* @property bodies
* @type {Array}
*/
* Current bodies in this island.
* @property bodies
* @type {Array}
*/
this.bodies = [];
}

View file

@ -12,30 +12,41 @@ module.exports = IslandSolver;
* @class IslandSolver
* @constructor
* @param {Solver} subsolver
* @param {Object} options
* @extends Solver
*/
function IslandSolver(subsolver){
Solver.call(this);
function IslandSolver(subsolver,options){
Solver.call(this,options);
var that = this;
/**
* The solver used in the workers.
* @property subsolver
* @type {Solver}
*/
* The solver used in the workers.
* @property subsolver
* @type {Solver}
*/
this.subsolver = subsolver;
/**
* Number of islands
* @property numIslands
* @type {number}
*/
* Number of islands. Read only.
* @property numIslands
* @type {number}
*/
this.numIslands = 0;
// Pooling of node objects saves some GC load
this._nodePool = [];
/**
* Fires before an island is solved.
* @event beforeSolveIsland
* @param {Island} island
*/
this.beforeSolveIslandEvent = {
type : "beforeSolveIsland",
island : null,
};
};
IslandSolver.prototype = new Object(Solver.prototype);
IslandSolver.prototype = new Solver();
function getUnvisitedNode(nodes){
var Nnodes = nodes.length;
@ -153,7 +164,11 @@ IslandSolver.prototype.solve = function(dt,world){
this.numIslands = n;
// Solve islands
var e = this.beforeSolveIslandEvent;
for(var i=0; i<islands.length; i++){
islands[i].solve(dt,this.subsolver);
var island = islands[i];
e.island = island;
this.emit(e);
island.solve(dt,this.subsolver);
}
};

View file

@ -1,4 +1,5 @@
var Utils = require('../utils/Utils');
var Utils = require('../utils/Utils')
, EventEmitter = require('../events/EventEmitter')
module.exports = Solver;
@ -6,22 +7,49 @@ module.exports = Solver;
* Base class for constraint solvers.
* @class Solver
* @constructor
* @extends {EventEmitter}
*/
function Solver(){
function Solver(options){
options = options || {};
EventEmitter.call(this);
/**
* Current equations in the solver.
*
* @property equations
* @type {Array}
*/
* Current equations in the solver.
*
* @property equations
* @type {Array}
*/
this.equations = [];
};
/**
* Function that is used to sort all equations before each solve.
* @property equationSortFunction
* @type {function|boolean}
*/
this.equationSortFunction = options.equationSortFunction || false;
};
Solver.prototype = new EventEmitter();
/**
* Method to be implemented in each subclass
* @method solve
* @param {Number} dt
* @param {World} world
*/
Solver.prototype.solve = function(dt,world){
throw new Error("Solver.solve should be implemented by subclasses!");
};
/**
* Sort all equations using the .equationSortFunction. Should be called by subclasses before solving.
* @method sortEquations
*/
Solver.prototype.sortEquations = function(){
if(this.equationSortFunction)
this.equations.sort(this.equationSortFunction);
};
/**
* Add an equation to be solved.
*

View file

@ -23,3 +23,11 @@ Utils.appendArray = function(a,b){
}
}
};
/**
* The array type to use for internal numeric computations.
* @type {Array}
* @static
* @property ARRAY_TYPE
*/
Utils.ARRAY_TYPE = Float32Array || Array;

File diff suppressed because it is too large Load diff

View file

@ -200,7 +200,7 @@ Phaser.Physics.Arcade.prototype = {
* @param {number} velocity - Any component of velocity (e.g. 20).
* @param {number} acceleration - Rate at which the velocity is changing.
* @param {number} drag - Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set.
* @param {number} mMax - An absolute value cap for the velocity.
* @param {number} [max=10000] - An absolute value cap for the velocity.
* @return {number} The altered Velocity value.
*/
computeVelocity: function (axis, body, velocity, acceleration, drag, max) {

View file

@ -392,11 +392,10 @@ Phaser.Physics.Arcade.Body.prototype = {
this.checkWorldBounds();
}
this.hull.setTo(this.preX, this.preY, this.width, this.height);
// this.hull.setTo(this.preX, this.preY, this.width, this.height);
// this.hullX.setTo(this.x, this.preY, this.width, this.height);
// this.hullY.setTo(this.preX, this.y, this.width, this.height);
this.updateHulls(true);
// this.updateHulls(true);
}
if (this.skipQuadTree === false && this.allowCollision.none === false && this.sprite.visible && this.sprite.alive)

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB