Removed debug info and tidying up ready for release.

This commit is contained in:
photonstorm
2014-02-05 04:24:32 +00:00
parent 1af86771ba
commit 846e9c5061
46 changed files with 1644 additions and 16323 deletions
-8768
View File
File diff suppressed because it is too large Load Diff
-26
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -36,6 +36,12 @@ function test8() {
game.step();
});
$('#start').click(function(){
console.log('---- START DEBUGGING -------------------------------');
game.enableStep();
sprite2.debug = true;
});
game.physics.gravity.y = 200;
sprite = game.add.sprite(300, 300, 'atari');
@@ -48,7 +54,7 @@ function test8() {
sprite2.name = 'gameboy';
sprite2.body.collideWorldBounds = true;
sprite2.body.bounce.setTo(0.8, 0.8);
// sprite2.body.minVelocity.setTo(30, 30);
sprite2.body.minVelocity.setTo(10, 10);
// sprite.debug = true;
// game.enableStep();
@@ -1,44 +0,0 @@
var vec2 = require('../math/vec2')
module.exports = Broadphase;
/**
* Base class for broadphase implementations.
* @class Broadphase
* @constructor
*/
function Broadphase(){
/**
* The resulting overlapping pairs. Will be filled with results during .getCollisionPairs().
* @property result
* @type {Array}
*/
this.result = [];
};
/**
* Get all potential intersecting body pairs.
* @method getCollisionPairs
* @param {World} world The world to search in.
* @return {Array} An array of the bodies, ordered in pairs. Example: A result of [a,b,c,d] means that the potential pairs are: (a,b), (c,d).
*/
Broadphase.prototype.getCollisionPairs = function(world){
throw new Error("getCollisionPairs must be implemented in a subclass!");
};
var dist = vec2.create();
/**
* Check whether the bounding radius of two bodies overlap.
* @method boundingRadiusCheck
* @param {Body} bodyA
* @param {Body} bodyB
* @return {Boolean}
*/
Broadphase.boundingRadiusCheck = function(bodyA, bodyB){
vec2.sub(dist, bodyA.position, bodyB.position);
var d2 = vec2.squaredLength(dist),
r = bodyA.boundingRadius + bodyB.boundingRadius;
return d2 <= r*r;
};
@@ -1,159 +0,0 @@
var Circle = require('../shapes/Circle')
, Plane = require('../shapes/Plane')
, Particle = require('../shapes/Particle')
, Broadphase = require('../collision/Broadphase')
, vec2 = require('../math/vec2')
module.exports = GridBroadphase;
/**
* Broadphase that uses axis-aligned bins.
* @class GridBroadphase
* @constructor
* @extends Broadphase
* @param {number} xmin Lower x bound of the grid
* @param {number} xmax Upper x bound
* @param {number} ymin Lower y bound
* @param {number} ymax Upper y bound
* @param {number} nx Number of bins along x axis
* @param {number} ny Number of bins along y axis
* @todo test
*/
function GridBroadphase(xmin,xmax,ymin,ymax,nx,ny){
Broadphase.apply(this);
nx = nx || 10;
ny = ny || 10;
this.binsizeX = (xmax-xmin) / nx;
this.binsizeY = (ymax-ymin) / ny;
this.nx = nx;
this.ny = ny;
this.xmin = xmin;
this.ymin = ymin;
this.xmax = xmax;
this.ymax = ymax;
};
GridBroadphase.prototype = new Broadphase();
/**
* Get a bin index given a world coordinate
* @method getBinIndex
* @param {Number} x
* @param {Number} y
* @return {Number} Integer index
*/
GridBroadphase.prototype.getBinIndex = function(x,y){
var nx = this.nx,
ny = this.ny,
xmin = this.xmin,
ymin = this.ymin,
xmax = this.xmax,
ymax = this.ymax;
var xi = Math.floor(nx * (x - xmin) / (xmax-xmin));
var yi = Math.floor(ny * (y - ymin) / (ymax-ymin));
return xi*ny + yi;
}
/**
* Get collision pairs.
* @method getCollisionPairs
* @param {World} world
* @return {Array}
*/
GridBroadphase.prototype.getCollisionPairs = function(world){
var result = [],
collidingBodies = world.bodies,
Ncolliding = Ncolliding=collidingBodies.length,
binsizeX = this.binsizeX,
binsizeY = this.binsizeY;
var bins=[], Nbins=nx*ny;
for(var i=0; i<Nbins; i++)
bins.push([]);
var xmult = nx / (xmax-xmin);
var ymult = ny / (ymax-ymin);
// Put all bodies into bins
for(var i=0; i!==Ncolliding; i++){
var bi = collidingBodies[i];
var si = bi.shape;
if (si === undefined) {
continue;
} else if(si instanceof Circle){
// Put in bin
// check if overlap with other bins
var x = bi.position[0];
var y = bi.position[1];
var r = si.radius;
var xi1 = Math.floor(xmult * (x-r - xmin));
var yi1 = Math.floor(ymult * (y-r - ymin));
var xi2 = Math.floor(xmult * (x+r - xmin));
var yi2 = Math.floor(ymult * (y+r - ymin));
for(var j=xi1; j<=xi2; j++){
for(var k=yi1; k<=yi2; k++){
var xi = j;
var yi = k;
if(xi*(ny-1) + yi >= 0 && xi*(ny-1) + yi < Nbins)
bins[ xi*(ny-1) + yi ].push(bi);
}
}
} else if(si instanceof Plane){
// Put in all bins for now
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++){
var xi = k;
var yi = Math.floor(ymult * (binsizeY*j - ymin));
bins[ xi*(ny-1) + yi ].push(bi);
}
}
} else if(bi.angle == Math.PI*0.5){
var x = bi.position[0];
for(var j=0; j!==Nbins && xmin+binsizeX*(j-1)<x; j++){
for(var k=0; k<ny; k++){
var yi = k;
var xi = Math.floor(xmult * (binsizeX*j - xmin));
bins[ xi*(ny-1) + yi ].push(bi);
}
}
} else {
for(var j=0; j!==Nbins; j++)
bins[j].push(bi);
}
} else {
throw new Error("Shape not supported in GridBroadphase!");
}
}
// Check each bin
for(var i=0; i!==Nbins; i++){
var bin = bins[i];
for(var j=0, NbodiesInBin=bin.length; j!==NbodiesInBin; j++){
var bi = bin[j];
var si = bi.shape;
for(var k=0; k!==j; k++){
var bj = bin[k];
var sj = bj.shape;
if(si instanceof Circle){
if(sj instanceof Circle) c=Broadphase.circleCircle (bi,bj);
else if(sj instanceof Particle) c=Broadphase.circleParticle(bi,bj);
else if(sj instanceof Plane) c=Broadphase.circlePlane (bi,bj);
} else if(si instanceof Particle){
if(sj instanceof Circle) c=Broadphase.circleParticle(bj,bi);
} else if(si instanceof Plane){
if(sj instanceof Circle) c=Broadphase.circlePlane (bj,bi);
}
}
}
}
return result;
};
@@ -1,47 +0,0 @@
var Circle = require('../shapes/Circle')
, Plane = require('../shapes/Plane')
, Shape = require('../shapes/Shape')
, Particle = require('../shapes/Particle')
, Broadphase = require('../collision/Broadphase')
, vec2 = require('../math/vec2')
module.exports = NaiveBroadphase;
/**
* Naive broadphase implementation. Does N^2 tests.
*
* @class NaiveBroadphase
* @constructor
* @extends Broadphase
*/
function NaiveBroadphase(){
Broadphase.apply(this);
};
NaiveBroadphase.prototype = new Broadphase();
/**
* Get the colliding pairs
* @method getCollisionPairs
* @param {World} world
* @return {Array}
*/
NaiveBroadphase.prototype.getCollisionPairs = function(world){
var bodies = world.bodies,
result = this.result,
i, j, bi, bj;
result.length = 0;
for(i=0, Ncolliding=bodies.length; i!==Ncolliding; i++){
bi = bodies[i];
for(j=0; j<i; j++){
bj = bodies[j];
if(Broadphase.boundingRadiusCheck(bi,bj))
result.push(bi,bj);
}
}
return result;
};
File diff suppressed because it is too large Load Diff
-376
View File
@@ -1,376 +0,0 @@
var Plane = require("../shapes/Plane");
var Broadphase = require("../collision/Broadphase");
module.exports = {
QuadTree : QuadTree,
Node : Node,
BoundsNode : BoundsNode,
};
/**
* QuadTree data structure. See https://github.com/mikechambers/ExamplesByMesh/tree/master/JavaScript/QuadTree
* @class QuadTree
* @constructor
* @param {Object} An object representing the bounds of the top level of the QuadTree. The object
* should contain the following properties : x, y, width, height
* @param {Boolean} pointQuad Whether the QuadTree will contain points (true), or items with bounds
* (width / height)(false). Default value is false.
* @param {Number} maxDepth The maximum number of levels that the quadtree will create. Default is 4.
* @param {Number} maxChildren The maximum number of children that a node can contain before it is split into sub-nodes.
*/
function QuadTree(bounds, pointQuad, maxDepth, maxChildren){
var node;
if(pointQuad){
node = new Node(bounds, 0, maxDepth, maxChildren);
} else {
node = new BoundsNode(bounds, 0, maxDepth, maxChildren);
}
/**
* The root node of the QuadTree which covers the entire area being segmented.
* @property root
* @type Node
*/
this.root = node;
}
/**
* Inserts an item into the QuadTree.
* @method insert
* @param {Object|Array} item The item or Array of items to be inserted into the QuadTree. The item should expose x, y
* properties that represents its position in 2D space.
*/
QuadTree.prototype.insert = function(item){
if(item instanceof Array){
var len = item.length;
for(var i = 0; i < len; i++){
this.root.insert(item[i]);
}
} else {
this.root.insert(item);
}
}
/**
* Clears all nodes and children from the QuadTree
* @method clear
*/
QuadTree.prototype.clear = function(){
this.root.clear();
}
/**
* Retrieves all items / points in the same node as the specified item / point. If the specified item
* overlaps the bounds of a node, then all children in both nodes will be returned.
* @method retrieve
* @param {Object} item An object representing a 2D coordinate point (with x, y properties), or a shape
* with dimensions (x, y, width, height) properties.
*/
QuadTree.prototype.retrieve = function(item){
//get a copy of the array of items
var out = this.root.retrieve(item).slice(0);
return out;
}
QuadTree.prototype.getCollisionPairs = function(world){
var result = [];
// Add all bodies
this.insert(world.bodies);
/*
console.log("bodies",world.bodies.length);
console.log("maxDepth",this.root.maxDepth,"maxChildren",this.root.maxChildren);
*/
for(var i=0; i!==world.bodies.length; i++){
var b = world.bodies[i],
items = this.retrieve(b);
//console.log("items",items.length);
// Check results
for(var j=0, len=items.length; j!==len; j++){
var item = items[j];
if(b === item) continue; // Do not add self
// Check if they were already added
var found = false;
for(var k=0, numAdded=result.length; k<numAdded; k+=2){
var r1 = result[k],
r2 = result[k+1];
if( (r1==item && r2==b) || (r2==item && r1==b) ){
found = true;
break;
}
}
if(!found && Broadphase.boundingRadiusCheck(b,item)){
result.push(b,item);
}
}
}
//console.log("results",result.length);
// Clear until next
this.clear();
return result;
};
function Node(bounds, depth, maxDepth, maxChildren){
this.bounds = bounds;
this.children = [];
this.nodes = [];
if(maxChildren){
this.maxChildren = maxChildren;
}
if(maxDepth){
this.maxDepth = maxDepth;
}
if(depth){
this.depth = depth;
}
}
//subnodes
Node.prototype.classConstructor = Node;
//children contained directly in the node
Node.prototype.children = null;
//read only
Node.prototype.depth = 0;
Node.prototype.maxChildren = 4;
Node.prototype.maxDepth = 4;
Node.TOP_LEFT = 0;
Node.TOP_RIGHT = 1;
Node.BOTTOM_LEFT = 2;
Node.BOTTOM_RIGHT = 3;
Node.prototype.insert = function(item){
if(this.nodes.length){
var index = this.findIndex(item);
this.nodes[index].insert(item);
return;
}
this.children.push(item);
var len = this.children.length;
if(!(this.depth >= this.maxDepth) && len > this.maxChildren) {
this.subdivide();
for(var i = 0; i < len; i++){
this.insert(this.children[i]);
}
this.children.length = 0;
}
}
Node.prototype.retrieve = function(item){
if(this.nodes.length){
var index = this.findIndex(item);
return this.nodes[index].retrieve(item);
}
return this.children;
}
Node.prototype.findIndex = function(item){
var b = this.bounds;
var left = (item.position[0]-item.boundingRadius > b.x + b.width / 2) ? false : true;
var top = (item.position[1]-item.boundingRadius > b.y + b.height / 2) ? false : true;
if(item instanceof Plane){
left = top = false; // Will overlap the left/top boundary since it is infinite
}
//top left
var index = Node.TOP_LEFT;
if(left){
if(!top){
index = Node.BOTTOM_LEFT;
}
} else {
if(top){
index = Node.TOP_RIGHT;
} else {
index = Node.BOTTOM_RIGHT;
}
}
return index;
}
Node.prototype.subdivide = function(){
var depth = this.depth + 1;
var bx = this.bounds.x;
var by = this.bounds.y;
//floor the values
var b_w_h = (this.bounds.width / 2);
var b_h_h = (this.bounds.height / 2);
var bx_b_w_h = bx + b_w_h;
var by_b_h_h = by + b_h_h;
//top left
this.nodes[Node.TOP_LEFT] = new this.classConstructor({
x:bx,
y:by,
width:b_w_h,
height:b_h_h
},
depth);
//top right
this.nodes[Node.TOP_RIGHT] = new this.classConstructor({
x:bx_b_w_h,
y:by,
width:b_w_h,
height:b_h_h
},
depth);
//bottom left
this.nodes[Node.BOTTOM_LEFT] = new this.classConstructor({
x:bx,
y:by_b_h_h,
width:b_w_h,
height:b_h_h
},
depth);
//bottom right
this.nodes[Node.BOTTOM_RIGHT] = new this.classConstructor({
x:bx_b_w_h,
y:by_b_h_h,
width:b_w_h,
height:b_h_h
},
depth);
}
Node.prototype.clear = function(){
this.children.length = 0;
var len = this.nodes.length;
for(var i = 0; i < len; i++){
this.nodes[i].clear();
}
this.nodes.length = 0;
}
// BoundsQuadTree
function BoundsNode(bounds, depth, maxChildren, maxDepth){
Node.call(this, bounds, depth, maxChildren, maxDepth);
this.stuckChildren = [];
}
BoundsNode.prototype = new Node();
BoundsNode.prototype.classConstructor = BoundsNode;
BoundsNode.prototype.stuckChildren = null;
//we use this to collect and conctenate items being retrieved. This way
//we dont have to continuously create new Array instances.
//Note, when returned from QuadTree.retrieve, we then copy the array
BoundsNode.prototype.out = [];
BoundsNode.prototype.insert = function(item){
if(this.nodes.length){
var index = this.findIndex(item);
var node = this.nodes[index];
/*
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
item.position[0] - item.boundingRadius >= node.bounds.x &&
item.position[0] + item.boundingRadius <= node.bounds.x + node.bounds.width &&
item.position[1] - item.boundingRadius >= node.bounds.y &&
item.position[1] + item.boundingRadius <= node.bounds.y + node.bounds.height){
this.nodes[index].insert(item);
} else {
this.stuckChildren.push(item);
}
return;
}
this.children.push(item);
var len = this.children.length;
if(this.depth < this.maxDepth && len > this.maxChildren){
this.subdivide();
for(var i=0; i<len; i++){
this.insert(this.children[i]);
}
this.children.length = 0;
}
}
BoundsNode.prototype.getChildren = function(){
return this.children.concat(this.stuckChildren);
}
BoundsNode.prototype.retrieve = function(item){
var out = this.out;
out.length = 0;
if(this.nodes.length){
var index = this.findIndex(item);
out.push.apply(out, this.nodes[index].retrieve(item));
}
out.push.apply(out, this.stuckChildren);
out.push.apply(out, this.children);
return out;
}
BoundsNode.prototype.clear = function(){
this.stuckChildren.length = 0;
//array
this.children.length = 0;
var len = this.nodes.length;
if(!len){
return;
}
for(var i = 0; i < len; i++){
this.nodes[i].clear();
}
//array
this.nodes.length = 0;
//we could call the super clear function but for now, im just going to inline it
//call the hidden super.clear, and make sure its called with this = this instance
//Object.getPrototypeOf(BoundsNode.prototype).clear.call(this);
}
@@ -1,132 +0,0 @@
var Circle = require('../shapes/Circle')
, Plane = require('../shapes/Plane')
, Shape = require('../shapes/Shape')
, Particle = require('../shapes/Particle')
, Broadphase = require('../collision/Broadphase')
, vec2 = require('../math/vec2')
module.exports = SAP1DBroadphase;
/**
* Sweep and prune broadphase along one axis.
*
* @class SAP1DBroadphase
* @constructor
* @extends Broadphase
* @param {World} world
*/
function SAP1DBroadphase(world){
Broadphase.apply(this);
/**
* 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}
*/
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}
*/
this.axisIndex = 0;
// Add listeners to update the list of bodies.
var axisList = this.axisList;
world.on("addBody",function(e){
axisList.push(e.body);
}).on("removeBody",function(e){
var idx = axisList.indexOf(e.body);
if(idx !== -1)
axisList.splice(idx,1);
});
};
SAP1DBroadphase.prototype = new Broadphase();
/**
* Function for sorting bodies along the X axis. To be passed to array.sort()
* @method sortAxisListX
* @param {Body} bodyA
* @param {Body} bodyB
* @return {Number}
*/
SAP1DBroadphase.sortAxisListX = function(bodyA,bodyB){
return (bodyA.position[0]-bodyA.boundingRadius) - (bodyB.position[0]-bodyB.boundingRadius);
};
/**
* Function for sorting bodies along the Y axis. To be passed to array.sort()
* @method sortAxisListY
* @param {Body} bodyA
* @param {Body} bodyB
* @return {Number}
*/
SAP1DBroadphase.sortAxisListY = function(bodyA,bodyB){
return (bodyA.position[1]-bodyA.boundingRadius) - (bodyB.position[1]-bodyB.boundingRadius);
};
/**
* Get the colliding pairs
* @method getCollisionPairs
* @param {World} world
* @return {Array}
*/
SAP1DBroadphase.prototype.getCollisionPairs = function(world){
var bodies = this.axisList,
result = this.result,
axisIndex = this.axisIndex,
i,j;
result.length = 0;
// Sort the list
bodies.sort(axisIndex === 0 ? SAP1DBroadphase.sortAxisListX : SAP1DBroadphase.sortAxisListY );
// Look through the list
for(i=0, N=bodies.length; i!==N; i++){
var bi = bodies[i];
for(j=i+1; j<N; j++){
var bj = bodies[j];
if(!SAP1DBroadphase.checkBounds(bi,bj,axisIndex))
break;
// If we got overlap, add pair
if(Broadphase.boundingRadiusCheck(bi,bj))
result.push(bi,bj);
}
}
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;
};
@@ -1,42 +0,0 @@
module.exports = Constraint;
/**
* Base constraint class.
*
* @class Constraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
*/
function Constraint(bodyA,bodyB){
/**
* Equations to be solved in this constraint
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* First body participating in the constraint.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second body participating in the constraint.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
};
/**
* To be implemented by subclasses. Should update the internal constraint parameters.
* @method update
*/
/*Constraint.prototype.update = function(){
throw new Error("method update() not implmemented in this Constraint subclass!");
};*/
@@ -1,114 +0,0 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2'),
mat2 = require('../math/mat2');
module.exports = ContactEquation;
/**
* Non-penetration constraint equation. Tries to make the ri and rj vectors the same point.
*
* @class ContactEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function ContactEquation(bi,bj){
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();
/**
* 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;
ContactEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
xi = bi.position,
xj = bj.position;
var penetrationVec = this.penetrationVec,
n = this.ni,
G = this.G;
// Caluclate cross products
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);
// Compute iteration
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;
};
@@ -1,85 +0,0 @@
var Constraint = require('./Constraint')
, Equation = require('./Equation')
, vec2 = require('../math/vec2')
module.exports = DistanceConstraint;
/**
* Constraint that tries to keep the distance between two bodies constant.
*
* @class DistanceConstraint
* @constructor
* @author schteppe
* @param {Body} bodyA
* @param {Body} bodyB
* @param {number} dist The distance to keep between the bodies.
* @param {number} maxForce
* @extends {Constraint}
*/
function DistanceConstraint(bodyA,bodyB,distance,maxForce){
Constraint.call(this,bodyA,bodyB);
/**
* The distance to keep.
* @property distance
* @type {Number}
*/
this.distance = distance;
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);
}
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,
G = normal.G;
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;
};
@@ -1,316 +0,0 @@
module.exports = Equation;
var vec2 = require('../math/vec2'),
mat2 = require('../math/mat2'),
Utils = require('../utils/Utils');
/**
* Base class for constraint equations.
* @class Equation
* @constructor
* @param {Body} bi First body participating in the equation
* @param {Body} bj Second body participating in the equation
* @param {number} minForce Minimum force to apply. Default: -1e6
* @param {number} maxForce Maximum force to apply. Default: 1e6
*/
function Equation(bi,bj,minForce,maxForce){
/**
* Minimum force to apply when solving
* @property minForce
* @type {Number}
*/
this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce;
/**
* Max force to apply when solving
* @property maxForce
* @type {Number}
*/
this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce;
/**
* First body participating in the constraint
* @property bi
* @type {Body}
*/
this.bi = bi;
/**
* Second body participating in the constraint
* @property bj
* @type {Body}
*/
this.bj = bj;
/**
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
* @property stiffness
* @type {Number}
*/
this.stiffness = 1e6;
/**
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
* @property relaxation
* @type {Number}
*/
this.relaxation = 4;
/**
* 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;
/**
* Update SPOOK parameters .a, .b and .eps according to the given time step. See equations 9, 10 and 11 in the <a href="http://www8.cs.umu.se/kurser/5DV058/VT09/lectures/spooknotes.pdf">SPOOK notes</a>.
* @method updateSpookParams
* @param {number} timeStep
*/
Equation.prototype.updateSpookParams = function(timeStep){
var k = this.stiffness,
d = this.relaxation,
h = timeStep;
this.a = 4.0 / (h * (1 + 4 * d));
this.b = (4.0 * d) / (1 + 4 * d);
this.eps = 4.0 / (h * h * k * (1 + 4 * d));
this.h = timeStep;
};
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;
};
@@ -1,108 +0,0 @@
var mat2 = require('../math/mat2')
, vec2 = require('../math/vec2')
, Equation = require('./Equation')
, Utils = require('../utils/Utils')
module.exports = FrictionEquation;
/**
* Constrains the slipping in a contact along a tangent
*
* @class FrictionEquation
* @constructor
* @param {Body} bi
* @param {Body} bj
* @param {Number} slipForce
* @extends {Equation}
*/
function FrictionEquation(bi,bj,slipForce){
Equation.call(this,bi,bj,-slipForce,slipForce);
/**
* Relative vector from center of body i to the contact point, in world coords.
* @property ri
* @type {Float32Array}
*/
this.ri = vec2.create();
/**
* Relative vector from center of body j to the contact point, in world coords.
* @property rj
* @type {Float32Array}
*/
this.rj = vec2.create();
/**
* Tangent vector that the friction force will act along, in world coords.
* @property t
* @type {Float32Array}
*/
this.t = vec2.create();
/**
* 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;
/**
* Set the slipping condition for the constraint. The friction force cannot be
* larger than this value.
* @method setSlipForce
* @param {Number} slipForce
* @deprecated Use .frictionCoefficient instead
*/
FrictionEquation.prototype.setSlipForce = function(slipForce){
this.maxForce = slipForce;
this.minForce = -slipForce;
};
FrictionEquation.prototype.computeB = function(a,b,h){
var bi = this.bi,
bj = this.bj,
ri = this.ri,
rj = this.rj,
t = this.t,
G = this.G;
// 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 = this.computeGW();
var GiMf = this.computeGiMf();
var B = /* - g * a */ - GW * b - h*GiMf;
return B;
};
@@ -1,123 +0,0 @@
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);
};
@@ -1,117 +0,0 @@
var Constraint = require('./Constraint')
, ContactEquation = require('./ContactEquation')
, Equation = require('./Equation')
, vec2 = require('../math/vec2')
, RotationalLockEquation = require('./RotationalLockEquation')
module.exports = PrismaticConstraint;
/**
* 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 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);
// 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);
/**
* @property localAnchorA
* @type {Array}
*/
this.localAnchorA = localAnchorA;
/**
* @property localAnchorB
* @type {Array}
*/
this.localAnchorB = localAnchorB;
/**
* @property localAxisA
* @type {Array}
*/
this.localAxisA = localAxisA;
/*
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);
}
var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce);
this.equations.push(trans,rot);
}
PrismaticConstraint.prototype = new Constraint();
/**
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
* @method update
*/
PrismaticConstraint.prototype.update = function(){
var eqs = this.equations,
trans = eqs[0];
trans.update();
};
@@ -1,208 +0,0 @@
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;
};
@@ -1,37 +0,0 @@
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);
};
@@ -1,32 +0,0 @@
var Equation = require("./Equation"),
vec2 = require('../math/vec2');
module.exports = RotationalVelocityEquation;
/**
* Syncs rotational velocity of two bodies, or sets a relative velocity (motor).
*
* @class RotationalVelocityEquation
* @constructor
* @extends Equation
* @param {Body} bi
* @param {Body} bj
*/
function RotationalVelocityEquation(bi,bj){
Equation.call(this,bi,bj,-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 G = this.G;
G[2] = -1;
G[5] = this.ratio;
var GiMf = this.computeGiMf();
var GW = this.computeGW() + this.relativeVelocity;
var B = - GW * b - h*GiMf;
return B;
};
@@ -1,84 +0,0 @@
/**
* Base class for objects that dispatches events.
* @class EventEmitter
* @constructor
*/
var EventEmitter = function () {}
module.exports = EventEmitter;
EventEmitter.prototype = {
constructor: EventEmitter,
/**
* 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;
if ( listeners[ type ] === undefined ) {
listeners[ type ] = [];
}
if ( listeners[ type ].indexOf( listener ) === - 1 ) {
listeners[ type ].push( listener );
}
return this;
},
/**
* 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;
if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
return true;
}
return false;
},
/**
* 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;
var index = listeners[ type ].indexOf( listener );
if ( index !== - 1 ) {
listeners[ type ].splice( index, 1 );
}
return this;
},
/**
* 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;
var listenerArray = listeners[ event.type ];
if ( listenerArray !== undefined ) {
event.target = this;
for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
listenerArray[ i ].call( this, event );
}
}
return this;
}
};
@@ -1,81 +0,0 @@
module.exports = ContactMaterial;
var idCounter = 0;
/**
* Defines a physics material.
* @class ContactMaterial
* @constructor
* @param {Material} materialA
* @param {Material} materialB
* @param {Object} [options]
* @param {Number} options.friction
* @param {Number} options.restitution
* @author schteppe
*/
function ContactMaterial(materialA, materialB, options){
options = options || {};
/**
* The contact material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
/**
* 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}
*/
this.materialB = materialB;
/**
* 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.0;
/**
* 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}
*/
this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3;
/**
* 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}
*/
this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3;
};
-19
View File
@@ -1,19 +0,0 @@
module.exports = Material;
var idCounter = 0;
/**
* Defines a physics material.
* @class Material
* @constructor
* @param string name
* @author schteppe
*/
function Material(){
/**
* The material identifier
* @property id
* @type {Number}
*/
this.id = idCounter++;
};
-10
View File
@@ -1,10 +0,0 @@
/**
* The mat2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
* @class mat2
*/
// Only import mat2 from gl-matrix and skip the rest
var mat2 = require('../../node_modules/gl-matrix/src/gl-matrix/mat2').mat2;
// Export everything
module.exports = mat2;
-477
View File
@@ -1,477 +0,0 @@
/*
PolyK library
url: http://polyk.ivank.net
Released under MIT licence.
Copyright (c) 2012 Ivan Kuckir
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
var PolyK = {};
/*
Is Polygon self-intersecting?
O(n^2)
*/
/*
PolyK.IsSimple = function(p)
{
var n = p.length>>1;
if(n<4) return true;
var a1 = new PolyK._P(), a2 = new PolyK._P();
var b1 = new PolyK._P(), b2 = new PolyK._P();
var c = new PolyK._P();
for(var i=0; i<n; i++)
{
a1.x = p[2*i ];
a1.y = p[2*i+1];
if(i==n-1) { a2.x = p[0 ]; a2.y = p[1 ]; }
else { a2.x = p[2*i+2]; a2.y = p[2*i+3]; }
for(var j=0; j<n; j++)
{
if(Math.abs(i-j) < 2) continue;
if(j==n-1 && i==0) continue;
if(i==n-1 && j==0) continue;
b1.x = p[2*j ];
b1.y = p[2*j+1];
if(j==n-1) { b2.x = p[0 ]; b2.y = p[1 ]; }
else { b2.x = p[2*j+2]; b2.y = p[2*j+3]; }
if(PolyK._GetLineIntersection(a1,a2,b1,b2,c) != null) return false;
}
}
return true;
}
PolyK.IsConvex = function(p)
{
if(p.length<6) return true;
var l = p.length - 4;
for(var i=0; i<l; i+=2)
if(!PolyK._convex(p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5])) return false;
if(!PolyK._convex(p[l ], p[l+1], p[l+2], p[l+3], p[0], p[1])) return false;
if(!PolyK._convex(p[l+2], p[l+3], p[0 ], p[1 ], p[2], p[3])) return false;
return true;
}
*/
PolyK.GetArea = function(p)
{
if(p.length <6) return 0;
var l = p.length - 2;
var sum = 0;
for(var i=0; i<l; i+=2)
sum += (p[i+2]-p[i]) * (p[i+1]+p[i+3]);
sum += (p[0]-p[l]) * (p[l+1]+p[1]);
return - sum * 0.5;
}
/*
PolyK.GetAABB = function(p)
{
var minx = Infinity;
var miny = Infinity;
var maxx = -minx;
var maxy = -miny;
for(var i=0; i<p.length; i+=2)
{
minx = Math.min(minx, p[i ]);
maxx = Math.max(maxx, p[i ]);
miny = Math.min(miny, p[i+1]);
maxy = Math.max(maxy, p[i+1]);
}
return {x:minx, y:miny, width:maxx-minx, height:maxy-miny};
}
*/
PolyK.Triangulate = function(p)
{
var n = p.length>>1;
if(n<3) return [];
var tgs = [];
var avl = [];
for(var i=0; i<n; i++) avl.push(i);
var i = 0;
var al = n;
while(al > 3)
{
var i0 = avl[(i+0)%al];
var i1 = avl[(i+1)%al];
var i2 = avl[(i+2)%al];
var ax = p[2*i0], ay = p[2*i0+1];
var bx = p[2*i1], by = p[2*i1+1];
var cx = p[2*i2], cy = p[2*i2+1];
var earFound = false;
if(PolyK._convex(ax, ay, bx, by, cx, cy))
{
earFound = true;
for(var j=0; j<al; j++)
{
var vi = avl[j];
if(vi==i0 || vi==i1 || vi==i2) continue;
if(PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) {earFound = false; break;}
}
}
if(earFound)
{
tgs.push(i0, i1, i2);
avl.splice((i+1)%al, 1);
al--;
i= 0;
}
else if(i++ > 3*al) break; // no convex angles :(
}
tgs.push(avl[0], avl[1], avl[2]);
return tgs;
}
/*
PolyK.ContainsPoint = function(p, px, py)
{
var n = p.length>>1;
var ax, ay, bx = p[2*n-2]-px, by = p[2*n-1]-py;
var depth = 0;
for(var i=0; i<n; i++)
{
ax = bx; ay = by;
bx = p[2*i ] - px;
by = p[2*i+1] - py;
if(ay< 0 && by< 0) continue; // both "up" or both "donw"
if(ay>=0 && by>=0) continue; // both "up" or both "donw"
if(ax< 0 && bx< 0) continue;
var lx = ax + (bx-ax)*(-ay)/(by-ay);
if(lx>0) depth++;
}
return (depth & 1) == 1;
}
PolyK.Slice = function(p, ax, ay, bx, by)
{
if(PolyK.ContainsPoint(p, ax, ay) || PolyK.ContainsPoint(p, bx, by)) return [p.slice(0)];
var a = new PolyK._P(ax, ay);
var b = new PolyK._P(bx, by);
var iscs = []; // intersections
var ps = []; // points
for(var i=0; i<p.length; i+=2) ps.push(new PolyK._P(p[i], p[i+1]));
for(var i=0; i<ps.length; i++)
{
var isc = new PolyK._P(0,0);
isc = PolyK._GetLineIntersection(a, b, ps[i], ps[(i+1)%ps.length], isc);
if(isc)
{
isc.flag = true;
iscs.push(isc);
ps.splice(i+1,0,isc);
i++;
}
}
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);
var pgs = [];
var dir = 0;
while(iscs.length > 0)
{
var n = ps.length;
var i0 = iscs[0];
var i1 = iscs[1];
var ind0 = ps.indexOf(i0);
var ind1 = ps.indexOf(i1);
var solved = false;
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
else
{
i0 = iscs[1];
i1 = iscs[0];
ind0 = ps.indexOf(i0);
ind1 = ps.indexOf(i1);
if(PolyK._firstWithFlag(ps, ind0) == ind1) solved = true;
}
if(solved)
{
dir--;
var pgn = PolyK._getPoints(ps, ind0, ind1);
pgs.push(pgn);
ps = PolyK._getPoints(ps, ind1, ind0);
i0.flag = i1.flag = false;
iscs.splice(0,2);
if(iscs.length == 0) pgs.push(ps);
}
else { dir++; iscs.reverse(); }
if(dir>1) break;
}
var result = [];
for(var i=0; i<pgs.length; i++)
{
var pg = pgs[i];
var npg = [];
for(var j=0; j<pg.length; j++) npg.push(pg[j].x, pg[j].y);
result.push(npg);
}
return result;
}
PolyK.Raycast = function(p, x, y, dx, dy, isc)
{
var l = p.length - 2;
var tp = PolyK._tp;
var a1 = tp[0], a2 = tp[1],
b1 = tp[2], b2 = tp[3], c = tp[4];
a1.x = x; a1.y = y;
a2.x = x+dx; a2.y = y+dy;
if(isc==null) isc = {dist:0, edge:0, norm:{x:0, y:0}, refl:{x:0, y:0}};
isc.dist = Infinity;
for(var i=0; i<l; i+=2)
{
b1.x = p[i ]; b1.y = p[i+1];
b2.x = p[i+2]; b2.y = p[i+3];
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c);
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, i/2, isc);
}
b1.x = b2.x; b1.y = b2.y;
b2.x = p[0]; b2.y = p[1];
var nisc = PolyK._RayLineIntersection(a1, a2, b1, b2, c);
if(nisc) PolyK._updateISC(dx, dy, a1, b1, b2, c, p.length/2, isc);
return (isc.dist != Infinity) ? isc : null;
}
PolyK.ClosestEdge = function(p, x, y, isc)
{
var l = p.length - 2;
var tp = PolyK._tp;
var a1 = tp[0],
b1 = tp[2], b2 = tp[3], c = tp[4];
a1.x = x; a1.y = y;
if(isc==null) isc = {dist:0, edge:0, point:{x:0, y:0}, norm:{x:0, y:0}};
isc.dist = Infinity;
for(var i=0; i<l; i+=2)
{
b1.x = p[i ]; b1.y = p[i+1];
b2.x = p[i+2]; b2.y = p[i+3];
PolyK._pointLineDist(a1, b1, b2, i>>1, isc);
}
b1.x = b2.x; b1.y = b2.y;
b2.x = p[0]; b2.y = p[1];
PolyK._pointLineDist(a1, b1, b2, l>>1, isc);
var idst = 1/isc.dist;
isc.norm.x = (x-isc.point.x)*idst;
isc.norm.y = (y-isc.point.y)*idst;
return isc;
}
PolyK._pointLineDist = function(p, a, b, edge, isc)
{
var x = p.x, y = p.y, x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y;
var A = x - x1;
var B = y - y1;
var C = x2 - x1;
var D = y2 - y1;
var dot = A * C + B * D;
var len_sq = C * C + D * D;
var param = dot / len_sq;
var xx, yy;
if (param < 0 || (x1 == x2 && y1 == y2)) {
xx = x1;
yy = y1;
}
else if (param > 1) {
xx = x2;
yy = y2;
}
else {
xx = x1 + param * C;
yy = y1 + param * D;
}
var dx = x - xx;
var dy = y - yy;
var dst = Math.sqrt(dx * dx + dy * dy);
if(dst<isc.dist)
{
isc.dist = dst;
isc.edge = edge;
isc.point.x = xx;
isc.point.y = yy;
}
}
PolyK._updateISC = function(dx, dy, a1, b1, b2, c, edge, isc)
{
var nrl = PolyK._P.dist(a1, c);
if(nrl<isc.dist)
{
var ibl = 1/PolyK._P.dist(b1, b2);
var nx = -(b2.y-b1.y)*ibl;
var ny = (b2.x-b1.x)*ibl;
var ddot = 2*(dx*nx+dy*ny);
isc.dist = nrl;
isc.norm.x = nx;
isc.norm.y = ny;
isc.refl.x = -ddot*nx+dx;
isc.refl.y = -ddot*ny+dy;
isc.edge = edge;
}
}
PolyK._getPoints = function(ps, ind0, ind1)
{
var n = ps.length;
var nps = [];
if(ind1<ind0) ind1 += n;
for(var i=ind0; i<= ind1; i++) nps.push(ps[i%n]);
return nps;
}
PolyK._firstWithFlag = function(ps, ind)
{
var n = ps.length;
while(true)
{
ind = (ind+1)%n;
if(ps[ind].flag) return ind;
}
}
*/
PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy)
{
var v0x = cx-ax;
var v0y = cy-ay;
var v1x = bx-ax;
var v1y = by-ay;
var v2x = px-ax;
var v2y = py-ay;
var dot00 = v0x*v0x+v0y*v0y;
var dot01 = v0x*v1x+v0y*v1y;
var dot02 = v0x*v2x+v0y*v2y;
var dot11 = v1x*v1x+v1y*v1y;
var dot12 = v1x*v2x+v1y*v2y;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
return (u >= 0) && (v >= 0) && (u + v < 1);
}
/*
PolyK._RayLineIntersection = function(a1, a2, b1, b2, c)
{
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x);
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
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);
var I = c;
var iDen = 1/Den;
I.x = ( A*dbx - dax*B ) * iDen;
I.y = ( A*dby - day*B ) * iDen;
if(!PolyK._InRect(I, b1, b2)) return null;
if((day>0 && I.y>a1.y) || (day<0 && I.y<a1.y)) return null;
if((dax>0 && I.x>a1.x) || (dax<0 && I.x<a1.x)) return null;
return I;
}
PolyK._GetLineIntersection = function(a1, a2, b1, b2, c)
{
var dax = (a1.x-a2.x), dbx = (b1.x-b2.x);
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
var Den = dax*dby - day*dbx;
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);
var I = c;
I.x = ( A*dbx - dax*B ) / Den;
I.y = ( A*dby - day*B ) / Den;
if(PolyK._InRect(I, a1, a2) && PolyK._InRect(I, b1, b2)) return I;
return null;
}
PolyK._InRect = function(a, b, c)
{
if (b.x == c.x) return (a.y>=Math.min(b.y, c.y) && a.y<=Math.max(b.y, c.y));
if (b.y == c.y) return (a.x>=Math.min(b.x, c.x) && a.x<=Math.max(b.x, c.x));
if(a.x >= Math.min(b.x, c.x) && a.x <= Math.max(b.x, c.x)
&& a.y >= Math.min(b.y, c.y) && a.y <= Math.max(b.y, c.y))
return true;
return false;
}
*/
PolyK._convex = function(ax, ay, bx, by, cx, cy)
{
return (ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0;
}
/*
PolyK._P = function(x,y)
{
this.x = x;
this.y = y;
this.flag = false;
}
PolyK._P.prototype.toString = function()
{
return "Point ["+this.x+", "+this.y+"]";
}
PolyK._P.dist = function(a,b)
{
var dx = b.x-a.x;
var dy = b.y-a.y;
return Math.sqrt(dx*dx + dy*dy);
}
PolyK._tp = [];
for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0));
*/
module.exports = PolyK;
-122
View File
@@ -1,122 +0,0 @@
/**
* The vec2 object from glMatrix, extended with the functions documented here. See http://glmatrix.net for full doc.
* @class vec2
*/
// Only import vec2 from gl-matrix and skip the rest
var vec2 = require('../../node_modules/gl-matrix/src/gl-matrix/vec2').vec2;
// Now add some extensions
/**
* Get the vector x component
* @method getX
* @static
* @param {Float32Array} a
* @return {Number}
*/
vec2.getX = function(a){
return a[0];
};
/**
* Get the vector y component
* @method getY
* @static
* @param {Float32Array} a
* @return {Number}
*/
vec2.getY = function(a){
return a[1];
};
/**
* Make a cross product and only return the z component
* @method crossLength
* @static
* @param {Float32Array} a
* @param {Float32Array} b
* @return {Number}
*/
vec2.crossLength = function(a,b){
return a[0] * b[1] - a[1] * b[0];
};
/**
* Cross product between a vector and the Z component of a vector
* @method crossVZ
* @static
* @param {Float32Array} out
* @param {Float32Array} vec
* @param {Number} zcomp
* @return {Number}
*/
vec2.crossVZ = function(out, vec, zcomp){
vec2.rotate(out,vec,-Math.PI/2);// Rotate according to the right hand rule
vec2.scale(out,out,zcomp); // Scale with z
return out;
};
/**
* Cross product between a vector and the Z component of a vector
* @method crossZV
* @static
* @param {Float32Array} out
* @param {Number} zcomp
* @param {Float32Array} vec
* @return {Number}
*/
vec2.crossZV = function(out, zcomp, vec){
vec2.rotate(out,vec,Math.PI/2); // Rotate according to the right hand rule
vec2.scale(out,out,zcomp); // Scale with z
return out;
};
/**
* Rotate a vector by an angle
* @method rotate
* @static
* @param {Float32Array} out
* @param {Float32Array} a
* @param {Number} angle
*/
vec2.rotate = function(out,a,angle){
var c = Math.cos(angle),
s = Math.sin(angle),
x = a[0],
y = a[1];
out[0] = c*x -s*y;
out[1] = s*x +c*y;
};
vec2.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){
vec2.copy(out, worldPoint);
vec2.sub(out, out, framePosition);
vec2.rotate(out, out, -frameAngle);
};
vec2.toGlobalFrame = function(out, localPoint, framePosition, frameAngle){
vec2.copy(out, localPoint);
vec2.rotate(out, out, frameAngle);
vec2.add(out, out, framePosition);
};
/**
* Compute centroid of a triangle spanned by vectors a,b,c. See http://easycalculation.com/analytical/learn-centroid.php
* @method centroid
* @static
* @param {Float32Array} out
* @param {Float32Array} a
* @param {Float32Array} b
* @param {Float32Array} c
* @return {Float32Array} The out object
*/
vec2.centroid = function(out, a, b, c){
vec2.add(out, a, b);
vec2.add(out, out, c);
vec2.scale(out, out, 1/3);
return out;
};
// Export everything
module.exports = vec2;
-548
View File
@@ -1,548 +0,0 @@
var vec2 = require('../math/vec2')
, decomp = require('poly-decomp')
, Convex = require('../shapes/Convex')
module.exports = Body;
var zero = vec2.fromValues(0,0);
/**
* A rigid body. Has got a center of mass, position, velocity and a number of
* shapes that are used for collisions.
*
* @class Body
* @constructor
* @param {Object} [options]
* @param {Number} [options.mass=0] A number >= 0. If zero, the .motionState will be set to Body.STATIC.
* @param {Float32Array|Array} [options.position]
* @param {Float32Array|Array} [options.velocity]
* @param {Number} [options.angle=0]
* @param {Number} [options.angularVelocity=0]
* @param {Float32Array|Array} [options.force]
* @param {Number} [options.angularForce=0]
*
* @todo Should not take mass as argument to Body, but as density to each Shape
*/
function Body(options){
options = options || {};
/**
* 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}
*/
this.shapes = [];
/**
* 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}
*/
this.shapeAngles = [];
/**
* The mass of the body.
* @property mass
* @type {number}
*/
this.mass = options.mass || 0;
/**
* 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}
*/
this.inertia = 0;
/**
* The inverse inertia of the body.
* @property invInertia
* @type {number}
*/
this.invInertia = 0;
this.updateMassProperties();
/**
* 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}
*/
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}
*/
this.vlambda = vec2.fromValues(0,0);
/**
* 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}
*/
this.angle = options.angle || 0;
/**
* 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}
*/
this.force = vec2.create();
if(options.force) vec2.copy(this.force, options.force);
/**
* The angular force acting on the body
* @property angularForce
* @type {number}
*/
this.angularForce = options.angularForce || 0;
/**
* 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;
/**
* 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;
/**
* Update the bounding radius of the body. Should be done if any of the shapes
* are changed.
* @method updateBoundingRadius
*/
Body.prototype.updateBoundingRadius = function(){
var shapes = this.shapes,
shapeOffsets = this.shapeOffsets,
N = shapes.length,
radius = 0;
for(var i=0; i!==N; i++){
var shape = shapes[i],
offset = vec2.length(shapeOffsets[i] || zero),
r = shape.boundingRadius;
if(offset + r > radius)
radius = offset + r;
}
this.boundingRadius = radius;
};
/**
* Add a shape to the body. You can pass a local transform when adding a shape,
* so that the shape gets an offset and angle relative to the body center of mass.
* Will automatically update the mass properties and bounding radius.
*
* @method addShape
* @param {Shape} shape
* @param {Float32Array|Array} [offset] Local body offset of the shape.
* @param {Number} [angle] Local body angle.
*
* @example
* var body = new Body(),
* shape = new Circle();
*
* // Add the shape to the body, positioned in the center
* body.addShape(shape);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local x-axis.
* body.addShape(shape,[1,0]);
*
* // Add another shape to the body, positioned 1 unit length from the body center of mass along the local y-axis, and rotated 90 degrees CCW.
* 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);
this.updateMassProperties();
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.
*
* @method updateMassProperties
*
* @example
* body.mass += 1;
* body.updateMassProperties();
*/
Body.prototype.updateMassProperties = function(){
var shapes = this.shapes,
N = shapes.length,
m = this.mass / N,
I = 0;
for(var i=0; i<N; i++){
var shape = shapes[i],
r2 = vec2.squaredLength(this.shapeOffsets[i] || zero),
Icm = shape.computeMomentOfInertia(m);
I += Icm + m*r2;
}
this.inertia = I;
// Inverse mass properties are easy
this.invMass = this.mass > 0 ? 1/this.mass : 0;
this.invInertia = I>0 ? 1/I : 0;
};
var Body_applyForce_r = vec2.create();
/**
* Apply force to a world point. This could for example be a point on the RigidBody surface. Applying force this way will add to Body.force and Body.angularForce.
* @method applyForce
* @param {Float32Array} force The force to add.
* @param {Float32Array} worldPoint A world point to apply the force on.
*/
Body.prototype.applyForce = function(force,worldPoint){
// Compute point position relative to the body center
var r = Body_applyForce_r;
vec2.sub(r,worldPoint,this.position);
// Add linear force
vec2.add(this.force,this.force,force);
// Compute produced rotational force
var rotForce = vec2.crossLength(r,force);
// Add rotational force
this.angularForce += rotForce;
};
/**
* Transform a world point to local body frame.
* @method toLocalFrame
* @param {Float32Array|Array} out The vector to store the result in
* @param {Float32Array|Array} worldPoint The input world vector
*/
Body.prototype.toLocalFrame = function(out, worldPoint){
vec2.toLocalFrame(out, worldPoint, this.position, this.angle);
};
/**
* Transform a local point to world frame.
* @method toWorldFrame
* @param {Array} out The vector to store the result in
* @param {Array} localPoint The input local vector
*/
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
* @type {Number}
* @static
*/
Body.DYNAMIC = 1;
/**
* Static body.
* @property STATIC
* @type {Number}
* @static
*/
Body.STATIC = 2;
/**
* Kinematic body.
* @property KINEMATIC
* @type {Number}
* @static
*/
Body.KINEMATIC = 4;
-181
View File
@@ -1,181 +0,0 @@
var vec2 = require('../math/vec2');
module.exports = Spring;
/**
* A spring, connecting two bodies.
*
* @class Spring
* @constructor
* @param {Body} bodyA
* @param {Body} bodyB
* @param {Object} [options]
* @param {number} options.restLength A number > 0. Default: 1
* @param {number} options.stiffness A number >= 0. Default: 100
* @param {number} options.damping A number >= 0. Default: 1
* @param {Array} options.worldAnchorA Where to hook the spring to body A, in world coordinates.
* @param {Array} options.worldAnchorB
* @param {Array} options.localAnchorA Where to hook the spring to body A, in local body coordinates.
* @param {Array} options.localAnchorB
*/
function Spring(bodyA,bodyB,options){
options = options || {};
/**
* 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}
*/
this.stiffness = options.stiffness || 100;
/**
* Damping of the spring.
* @property damping
* @type {number}
*/
this.damping = options.damping || 1;
/**
* First connected body.
* @property bodyA
* @type {Body}
*/
this.bodyA = bodyA;
/**
* Second connected body.
* @property bodyB
* @type {Body}
*/
this.bodyB = bodyB;
/**
* 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}
*/
this.localAnchorB = vec2.fromValues(0,0);
if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA);
if(options.localAnchorB) vec2.copy(this.localAnchorB, options.localAnchorB);
if(options.worldAnchorA) this.setWorldAnchorA(options.worldAnchorA);
if(options.worldAnchorB) this.setWorldAnchorB(options.worldAnchorB);
};
/**
* Set the anchor point on body A, using world coordinates.
* @method setWorldAnchorA
* @param {Array} worldAnchorA
*/
Spring.prototype.setWorldAnchorA = function(worldAnchorA){
this.bodyA.toLocalFrame(this.localAnchorA, worldAnchorA);
};
/**
* Set the anchor point on body B, using world coordinates.
* @method setWorldAnchorB
* @param {Array} worldAnchorB
*/
Spring.prototype.setWorldAnchorB = function(worldAnchorB){
this.bodyB.toLocalFrame(this.localAnchorB, worldAnchorB);
};
/**
* Get the anchor point on body A, in world coordinates.
* @method getWorldAnchorA
* @param {Array} result The vector to store the result in.
*/
Spring.prototype.getWorldAnchorA = function(result){
this.bodyA.toWorldFrame(result, this.localAnchorA);
};
/**
* Get the anchor point on body B, in world coordinates.
* @method getWorldAnchorB
* @param {Array} result The vector to store the result in.
*/
Spring.prototype.getWorldAnchorB = function(result){
this.bodyB.toWorldFrame(result, this.localAnchorB);
};
var applyForce_r = vec2.create(),
applyForce_r_unit = vec2.create(),
applyForce_u = vec2.create(),
applyForce_f = vec2.create(),
applyForce_worldAnchorA = vec2.create(),
applyForce_worldAnchorB = vec2.create(),
applyForce_ri = vec2.create(),
applyForce_rj = vec2.create(),
applyForce_tmp = vec2.create();
/**
* Apply the spring force to the connected bodies.
* @method applyForce
*/
Spring.prototype.applyForce = function(){
var k = this.stiffness,
d = this.damping,
l = this.restLength,
bodyA = this.bodyA,
bodyB = this.bodyB,
r = applyForce_r,
r_unit = applyForce_r_unit,
u = applyForce_u,
f = applyForce_f,
tmp = applyForce_tmp;
var worldAnchorA = applyForce_worldAnchorA,
worldAnchorB = applyForce_worldAnchorB,
ri = applyForce_ri,
rj = applyForce_rj;
// Get world anchors
this.getWorldAnchorA(worldAnchorA);
this.getWorldAnchorB(worldAnchorB);
// Get offset points
vec2.sub(ri, worldAnchorA, bodyA.position);
vec2.sub(rj, worldAnchorB, bodyB.position);
// Compute distance vector between world anchor points
vec2.sub(r, worldAnchorB, worldAnchorA);
var rlen = vec2.len(r);
vec2.normalize(r_unit,r);
//console.log(rlen)
//console.log("A",vec2.str(worldAnchorA),"B",vec2.str(worldAnchorB))
// Compute relative velocity of the anchor points, u
vec2.sub(u, bodyB.velocity, bodyA.velocity);
vec2.crossZV(tmp, bodyB.angularVelocity, rj);
vec2.add(u, u, tmp);
vec2.crossZV(tmp, bodyA.angularVelocity, ri);
vec2.sub(u, u, tmp);
// F = - k * ( x - L ) - D * ( u )
vec2.scale(f, r_unit, -k*(rlen-l) - d*vec2.dot(u,r_unit));
// Add forces to bodies
vec2.sub( bodyA.force, bodyA.force, f);
vec2.add( bodyB.force, bodyB.force, f);
// Angular force
var ri_x_f = vec2.crossLength(ri, f);
var rj_x_f = vec2.crossLength(rj, f);
bodyA.angularForce -= ri_x_f;
bodyB.angularForce += rj_x_f;
};
-38
View File
@@ -1,38 +0,0 @@
// Export p2 classes
module.exports = {
Body : require('./objects/Body'),
Broadphase : require('./collision/Broadphase'),
Capsule : require('./shapes/Capsule'),
Circle : require('./shapes/Circle'),
Constraint : require('./constraints/Constraint'),
ContactEquation : require('./constraints/ContactEquation'),
ContactMaterial : require('./material/ContactMaterial'),
Convex : require('./shapes/Convex'),
DistanceConstraint : require('./constraints/DistanceConstraint'),
Equation : require('./constraints/Equation'),
EventEmitter : require('./events/EventEmitter'),
FrictionEquation : require('./constraints/FrictionEquation'),
GridBroadphase : require('./collision/GridBroadphase'),
GSSolver : require('./solver/GSSolver'),
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'),
RevoluteConstraint : require('./constraints/RevoluteConstraint'),
PrismaticConstraint : require('./constraints/PrismaticConstraint'),
Rectangle : require('./shapes/Rectangle'),
RotationalVelocityEquation : require('./constraints/RotationalVelocityEquation'),
SAP1DBroadphase : require('./collision/SAP1DBroadphase'),
Shape : require('./shapes/Shape'),
Solver : require('./solver/Solver'),
Spring : require('./objects/Spring'),
Utils : require('./utils/Utils'),
World : require('./world/World'),
QuadTree : require('./collision/QuadTree').QuadTree,
vec2 : require('./math/vec2'),
version : require('../package.json').version,
};
@@ -1,146 +0,0 @@
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,
};
-39
View File
@@ -1,39 +0,0 @@
var Shape = require('./Shape')
, vec2 = require('../math/vec2')
module.exports = Capsule;
/**
* Capsule shape class.
* @class Capsule
* @constructor
* @extends {Shape}
* @param {Number} length The distance between the end points
* @param {Number} radius Radius of the capsule
*/
function Capsule(length,radius){
this.length = length || 1;
this.radius = radius || 1;
Shape.call(this,Shape.CAPSULE);
};
Capsule.prototype = new Shape();
/**
* Compute the mass moment of inertia of the Capsule.
* @method conputeMomentOfInertia
* @param {Number} mass
* @return {Number}
* @todo
*/
Capsule.prototype.computeMomentOfInertia = function(mass){
// Approximate with rectangle
var r = this.radius,
w = this.length + r, // 2*r is too much, 0 is too little
h = r*2;
return mass * (h*h + w*w) / 12;
};
Capsule.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.radius + this.length/2;
};
-31
View File
@@ -1,31 +0,0 @@
var Shape = require('./Shape');
module.exports = Circle;
/**
* Circle shape class.
* @class Circle
* @extends {Shape}
* @constructor
* @param {number} radius The radius of this circle
*/
function Circle(radius){
/**
* The radius of the circle.
* @property radius
* @type {number}
*/
this.radius = radius || 1;
Shape.call(this,Shape.CIRCLE);
};
Circle.prototype = new Shape();
Circle.prototype.computeMomentOfInertia = function(mass){
var r = this.radius;
return mass * r * r / 2;
};
Circle.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.radius;
};
-255
View File
@@ -1,255 +0,0 @@
var Shape = require('./Shape')
, vec2 = require('../math/vec2')
, polyk = require('../math/polyk')
, decomp = require('poly-decomp')
module.exports = Convex;
/**
* Convex shape class.
* @class Convex
* @constructor
* @extends {Shape}
* @param {Array} vertices An array of Float32Array vertices that span this shape. Vertices are given in counter-clockwise (CCW) direction.
*/
function Convex(vertices){
/**
* 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}
*/
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}
*/
this.triangles = [];
if(this.vertices.length){
this.updateTriangles();
this.updateCenterOfMass();
}
/**
* The bounding radius of the convex
* @property boundingRadius
* @type {Number}
*/
this.boundingRadius = 0;
this.updateBoundingRadius();
Shape.call(this,Shape.CONVEX);
};
Convex.prototype = new Shape();
/**
* Update the .triangles property
* @method updateTriangles
*/
Convex.prototype.updateTriangles = function(){
this.triangles.length = 0;
// Rewrite on polyk notation, array of numbers
var polykVerts = [];
for(var i=0; i<this.vertices.length; i++){
var v = this.vertices[i];
polykVerts.push(v[0],v[1]);
}
// Triangulate
var triangles = polyk.Triangulate(polykVerts);
// Loop over all triangles, add their inertia contributions to I
for(var i=0; i<triangles.length; i+=3){
var id1 = triangles[i],
id2 = triangles[i+1],
id3 = triangles[i+2];
// Add to triangles
this.triangles.push([id1,id2,id3]);
}
};
var updateCenterOfMass_centroid = vec2.create(),
updateCenterOfMass_centroid_times_mass = vec2.create(),
updateCenterOfMass_a = vec2.create(),
updateCenterOfMass_b = vec2.create(),
updateCenterOfMass_c = vec2.create(),
updateCenterOfMass_ac = 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,
cm = this.centerOfMass,
centroid = updateCenterOfMass_centroid,
n = updateCenterOfMass_n,
a = updateCenterOfMass_a,
b = updateCenterOfMass_b,
c = updateCenterOfMass_c,
ac = updateCenterOfMass_ac,
ca = updateCenterOfMass_ca,
cb = updateCenterOfMass_cb,
centroid_times_mass = updateCenterOfMass_centroid_times_mass;
vec2.set(cm,0,0);
var totalArea = 0;
for(var i=0; i!==triangles.length; i++){
var t = triangles[i],
a = verts[t[0]],
b = verts[t[1]],
c = verts[t[2]];
vec2.centroid(centroid,a,b,c);
// 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)
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);
};
/**
* Compute the mass moment of inertia of the Convex.
* @method conputeMomentOfInertia
* @param {Number} mass
* @return {Number}
* @todo should use .triangles
*/
Convex.prototype.computeMomentOfInertia = function(mass){
// In short: Triangulate the Convex, compute centroid and inertia of
// each sub-triangle. Add up to total using parallel axis theorem.
var I = 0;
// Rewrite on polyk notation, array of numbers
var polykVerts = [];
for(var i=0; i<this.vertices.length; i++){
var v = this.vertices[i];
polykVerts.push(v[0],v[1]);
}
// Triangulate
var triangles = polyk.Triangulate(polykVerts);
// Get total convex area and density
var area = polyk.GetArea(polykVerts);
this.updateArea();
var density = mass / this.area;
// Temp vectors
var a = vec2.create(),
b = vec2.create(),
c = vec2.create(),
centroid = vec2.create(),
n = vec2.create(),
ac = vec2.create(),
ca = vec2.create(),
cb = vec2.create(),
centroid_times_mass = vec2.create();
// Loop over all triangles, add their inertia contributions to I
for(var i=0; i<triangles.length; i+=3){
var id1 = triangles[i],
id2 = triangles[i+1],
id3 = triangles[i+2];
// a,b,c are triangle corners
vec2.set(a, polykVerts[2*id1], polykVerts[2*id1+1]);
vec2.set(b, polykVerts[2*id2], polykVerts[2*id2+1]);
vec2.set(c, polykVerts[2*id3], polykVerts[2*id3+1]);
vec2.centroid(centroid, a, b, c);
vec2.sub(ca, c, a);
vec2.sub(cb, c, b);
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 mass for the triangle
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);
I += I_triangle + m*r2;
}
return I;
};
/**
* Updates the .boundingRadius property
* @method updateBoundingRadius
*/
Convex.prototype.updateBoundingRadius = function(){
var verts = this.vertices,
r2 = 0;
for(var i=0; i!==verts.length; i++){
var l2 = vec2.squaredLength(verts[i]);
if(l2 > r2) r2 = l2;
}
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;
}
};
-31
View File
@@ -1,31 +0,0 @@
var Shape = require('./Shape');
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}
*/
this.length = length;
Shape.call(this,Shape.LINE);
};
Line.prototype = new Shape();
Line.prototype.computeMomentOfInertia = function(mass){
return mass * Math.pow(this.length,2) / 12;
};
Line.prototype.updateBoundingRadius = function(){
this.boundingRadius = this.length/2;
};
-22
View File
@@ -1,22 +0,0 @@
var Shape = require('./Shape');
module.exports = Particle;
/**
* Particle shape class.
* @class Particle
* @constructor
* @extends {Shape}
*/
function Particle(){
Shape.call(this,Shape.PARTICLE);
};
Particle.prototype = new Shape();
Particle.prototype.computeMomentOfInertia = function(mass){
return 0; // Can't rotate a particle
};
Particle.prototype.updateBoundingRadius = function(){
this.boundingRadius = 0;
};
-31
View File
@@ -1,31 +0,0 @@
var Shape = require('./Shape');
module.exports = Plane;
/**
* Plane shape class. The plane is facing in the Y direction.
* @class Plane
* @extends {Shape}
* @constructor
*/
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;
};
-60
View File
@@ -1,60 +0,0 @@
var vec2 = require('../math/vec2')
, Shape = require('./Shape')
, Convex = require('./Convex')
module.exports = Rectangle;
/**
* Rectangle shape class.
* @class Rectangle
* @constructor
* @param {Number} w Width
* @param {Number} h Height
* @extends {Convex}
*/
function Rectangle(w,h){
var verts = [ vec2.fromValues(-w/2, -h/2),
vec2.fromValues( w/2, -h/2),
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);
};
Rectangle.prototype = new Convex();
/**
* Compute moment of inertia
* @method computeMomentOfInertia
* @param {Number} mass
* @return {Number}
*/
Rectangle.prototype.computeMomentOfInertia = function(mass){
var w = this.width,
h = this.height;
return mass * (h*h + w*w) / 12;
};
/**
* Update the bounding radius
* @method updateBoundingRadius
*/
Rectangle.prototype.updateBoundingRadius = function(){
var w = this.width,
h = this.height;
this.boundingRadius = Math.sqrt(w*w + h*h) / 2;
};
-107
View File
@@ -1,107 +0,0 @@
module.exports = Shape;
/**
* Base class for shapes.
* @class Shape
* @constructor
*/
function Shape(type){
this.type = type;
/**
* 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
* }
*/
this.collisionGroup = 1;
/**
* 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}
*/
this.material = null;
/**
* Area of this shape.
* @property area
* @type {Number}
*/
this.area = 0;
this.updateArea();
};
Shape.CIRCLE = 1;
Shape.PARTICLE = 2;
Shape.PLANE = 4;
Shape.CONVEX = 8;
Shape.LINE = 16;
Shape.RECTANGLE = 32;
Shape.CAPSULE = 64;
/**
* Should return the moment of inertia around the Z axis of the body given the total mass. See <a href="http://en.wikipedia.org/wiki/List_of_moments_of_inertia">Wikipedia's list of moments of inertia</a>.
* @method computeMomentOfInertia
* @param {Number} mass
* @return {Number} If the inertia is infinity or if the object simply isn't possible to rotate, return 0.
*/
Shape.prototype.computeMomentOfInertia = function(mass){
throw new Error("Shape.computeMomentOfInertia is not implemented in this Shape...");
};
/**
* Returns the bounding circle radius of this shape.
* @method updateBoundingRadius
* @return {Number}
*/
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
};
-209
View File
@@ -1,209 +0,0 @@
var vec2 = require('../math/vec2')
, Solver = require('./Solver')
, Utils = require('../utils/Utils')
, FrictionEquation = require('../constraints/FrictionEquation')
module.exports = GSSolver;
/**
* Iterative Gauss-Seidel constraint equation solver.
*
* @class GSSolver
* @constructor
* @extends Solver
* @param {Object} [options]
* @param {Number} options.iterations
* @param {Number} options.timeStep
* @param {Number} options.stiffness
* @param {Number} options.relaxation
* @param {Number} options.tolerance
*/
function GSSolver(options){
Solver.call(this,options);
options = options || {};
/**
* 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. 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. 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}
*/
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();
/**
* Solve the system of equations
* @method solve
* @param {Number} dt Time step
* @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,
bodies = world.bodies,
Nbodies = world.bodies.length,
h = dt,
d = this.relaxation,
k = this.stiffness,
eps = 4.0 / (h * h * k * (1 + 4 * d)),
a = 4.0 / (h * (1 + 4 * d)),
b = (4.0 * d) / (1 + 4 * d),
useGlobalParams = this.useGlobalEquationParameters,
add = vec2.add,
set = vec2.set,
useZeroRHS = this.useZeroRHS;
// Things that does not change during iteration can be computed once
if(this.lambda.length < Neq){
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,
lambda = this.lambda;
for(var i=0; i!==Neq; i++){
var c = equations[i];
lambda[i] = 0.0;
var _a = a,
_b = b,
_eps = eps;
if(!useGlobalParams){
if(h !== c.h) c.updateSpookParams(h);
_a = c.a;
_b = c.b;
_eps = c.eps;
}
Bs[i] = c.computeB(_a,_b,h);
invCs[i] = 1.0 / c.computeC(_eps);
}
var q, B, c, invC, deltalambda, deltalambdaTot, GWlambda, lambdaj;
if(Neq !== 0){
var i,j, minForce, maxForce, lambdaj_plus_deltalambda;
// Reset vlambda
for(i=0; i!==Nbodies; i++){
bodies[i].resetConstraintVelocity();
}
// Iterate over equations
for(iter=0; iter!==maxIter; iter++){
// Accumulate the total error for each iteration.
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;
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(tolSquared !== 0 && deltalambdaTot*deltalambdaTot <= tolSquared) break;
}
// Add result to velocity
for(i=0; i!==Nbodies; i++){
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;
};
-81
View File
@@ -1,81 +0,0 @@
module.exports = Island;
/**
* An island of bodies connected with equations.
* @class Island
* @constructor
*/
function Island(){
/**
* Current equations in this island.
* @property equations
* @type {Array}
*/
this.equations = [];
/**
* Current bodies in this island.
* @property bodies
* @type {Array}
*/
this.bodies = [];
}
/**
* Clean this island from bodies and equations.
* @method reset
*/
Island.prototype.reset = function(){
this.equations.length = this.bodies.length = 0;
}
/**
* Get all unique bodies in this island.
* @method getBodies
* @return {Array} An array of Body
*/
Island.prototype.getBodies = function(){
var bodies = [],
bodyIds = [],
eqs = this.equations;
for(var i=0; i!==eqs.length; i++){
var eq = eqs[i];
if(bodyIds.indexOf(eq.bi.id)===-1){
bodies.push(eq.bi);
bodyIds.push(eq.bi.id);
}
if(bodyIds.indexOf(eq.bj.id)===-1){
bodies.push(eq.bj);
bodyIds.push(eq.bj.id);
}
}
return bodies;
};
/**
* Solves all constraints in the group of islands.
* @method solve
* @param {Number} dt
* @param {Solver} solver
*/
Island.prototype.solve = function(dt,solver){
var bodies = [];
solver.removeAllEquations();
// Add equations to solver
var numEquations = this.equations.length;
for(var j=0; j!==numEquations; j++){
solver.addEquation(this.equations[j]);
}
var islandBodies = this.getBodies();
var numBodies = islandBodies.length;
for(var j=0; j!==numBodies; j++){
bodies.push(islandBodies[j]);
}
// Solve
solver.solve(dt,{bodies:bodies});
};
-174
View File
@@ -1,174 +0,0 @@
var Solver = require('./Solver')
, vec2 = require('../math/vec2')
, Island = require('../solver/Island')
, Body = require('../objects/Body')
, STATIC = Body.STATIC
module.exports = IslandSolver;
/**
* Splits the system of bodies and equations into independent islands
*
* @class IslandSolver
* @constructor
* @param {Solver} subsolver
* @param {Object} options
* @extends Solver
*/
function IslandSolver(subsolver,options){
Solver.call(this,options);
var that = this;
/**
* The solver used in the workers.
* @property subsolver
* @type {Solver}
*/
this.subsolver = subsolver;
/**
* 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 Solver();
function getUnvisitedNode(nodes){
var Nnodes = nodes.length;
for(var i=0; i!==Nnodes; i++){
var node = nodes[i];
if(!node.visited && !(node.body.motionState & STATIC)){ // correct?
return node;
}
}
return false;
}
function bfs(root,visitFunc){
var queue = [];
queue.push(root);
root.visited = true;
visitFunc(root);
while(queue.length) {
var node = queue.pop();
// Loop over unvisited child nodes
var child;
while((child = getUnvisitedNode(node.children))) {
child.visited = true;
visitFunc(child);
queue.push(child);
}
}
}
/**
* Solves the full system.
* @method solve
* @param {Number} dt
* @param {World} world
*/
IslandSolver.prototype.solve = function(dt,world){
var nodes = [],
bodies=world.bodies,
equations=this.equations,
Neq=equations.length,
Nbodies=bodies.length,
subsolver=this.subsolver,
workers = this._workers,
workerData = this._workerData,
workerIslandGroups = this._workerIslandGroups;
// Create needed nodes, reuse if possible
for(var i=0; i!==Nbodies; i++){
if(this._nodePool.length)
nodes.push( this._nodePool.pop() );
else {
nodes.push({
body:bodies[i],
children:[],
eqs:[],
visited:false
});
}
}
// Reset node values
for(var i=0; i!==Nbodies; i++){
var node = nodes[i];
node.body = bodies[i];
node.children.length = 0;
node.eqs.length = 0;
node.visited = false;
}
// Add connectivity data. Each equation connects 2 bodies.
for(var k=0; k!==Neq; k++){
var eq=equations[k],
i=bodies.indexOf(eq.bi),
j=bodies.indexOf(eq.bj),
ni=nodes[i],
nj=nodes[j];
ni.children.push(nj);
ni.eqs.push(eq);
nj.children.push(ni);
nj.eqs.push(eq);
}
// The BFS search algorithm needs a traversal function. What we do is gather all bodies and equations connected.
var child, n=0, eqs=[], bds=[];
function visitFunc(node){
bds.push(node.body);
var Neqs = node.eqs.length;
for(var i=0; i!==Neqs; i++){
var eq = node.eqs[i];
if(eqs.indexOf(eq) === -1){
eqs.push(eq);
}
}
}
// Get islands
var islands = [];
while((child = getUnvisitedNode(nodes))){
var island = new Island(); // @todo Should be reused from somewhere
eqs.length = 0;
bds.length = 0;
bfs(child,visitFunc); // run search algo to gather an island of bodies
// Add equations to island
var Neqs = eqs.length;
for(var i=0; i!==Neqs; i++){
var eq = eqs[i];
island.equations.push(eq);
}
n++;
islands.push(island);
}
this.numIslands = n;
// Solve islands
var e = this.beforeSolveIslandEvent;
for(var i=0; i<islands.length; i++){
var island = islands[i];
e.island = island;
this.emit(e);
island.solve(dt,this.subsolver);
}
};
-93
View File
@@ -1,93 +0,0 @@
var Utils = require('../utils/Utils')
, EventEmitter = require('../events/EventEmitter')
module.exports = Solver;
/**
* Base class for constraint solvers.
* @class Solver
* @constructor
* @extends {EventEmitter}
*/
function Solver(options){
options = options || {};
EventEmitter.call(this);
/**
* 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.
*
* @method addEquation
* @param {Equation} eq
*/
Solver.prototype.addEquation = function(eq){
this.equations.push(eq);
};
/**
* Add equations. Same as .addEquation, but this time the argument is an array of Equations
*
* @method addEquations
* @param {Array} eqs
*/
Solver.prototype.addEquations = function(eqs){
Utils.appendArray(this.equations,eqs);
};
/**
* Remove an equation.
*
* @method removeEquation
* @param {Equation} eq
*/
Solver.prototype.removeEquation = function(eq){
var i = this.equations.indexOf(eq);
if(i!=-1)
this.equations.splice(i,1);
};
/**
* Remove all currently added equations.
*
* @method removeAllEquations
*/
Solver.prototype.removeAllEquations = function(){
this.equations.length=0;
};
-33
View File
@@ -1,33 +0,0 @@
module.exports = Utils;
/**
* Misc utility functions
* @class Utils
* @constructor
*/
function Utils(){};
/**
* Append the values in array b to the array a. See <a href="http://stackoverflow.com/questions/1374126/how-to-append-an-array-to-an-existing-javascript-array/1374131#1374131">this</a> for an explanation.
* @method appendArray
* @static
* @param {Array} a
* @param {Array} b
*/
Utils.appendArray = function(a,b){
if (b.length < 150000) {
a.push.apply(a, b)
} else {
for (var i = 0, len = b.length; i !== len; ++i) {
a.push(b[i]);
}
}
};
/**
* 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
+1 -38
View File
@@ -234,11 +234,6 @@ Phaser.Physics.Arcade.prototype = {
rebounded = true;
}
if (body.sprite.debug)
{
console.log('checkBounds finished', rebounded, body.blocked);
}
return rebounded;
},
@@ -850,10 +845,6 @@ Phaser.Physics.Arcade.prototype = {
return this._intersection;
}
// console.log('____ tileIntersects');
// console.log('body: ', body.left, body.top, body.right, body.bottom);
// console.log('tile: ', tile.x, tile.y, tile.right, tile.bottom);
if (!(body.right < tile.x || body.bottom < tile.y || body.left > tile.right || body.top > tile.bottom))
{
this._intersection[0] = Math.max(body.left, tile.x); // x
@@ -866,6 +857,7 @@ Phaser.Physics.Arcade.prototype = {
}
this._intersection[4] = 0;
return this._intersection;
},
@@ -879,8 +871,6 @@ Phaser.Physics.Arcade.prototype = {
*/
separateTiles: function (body, tiles) {
// console.log('!!! separateTiles', tiles);
var tile;
var result = false;
@@ -912,15 +902,9 @@ Phaser.Physics.Arcade.prototype = {
// If the intersection area is either entirely null, or has a width/height of zero, we bail out now
if (this._intersection[4] === 0 || this._intersection[2] === 0 || this._intersection[3] === 0)
{
// console.log('Tile does not intersect body');
return false;
}
// console.log('*** separateTile', tile);
// console.log('intersection', this._intersection);
// tile.tile.debug = true;
// They overlap. Any custom callbacks?
if (tile.tile.callback || tile.layer.callbacks[tile.tile.index])
{
@@ -947,8 +931,6 @@ Phaser.Physics.Arcade.prototype = {
// LEFT
body.overlapX = body.left - tile.right;
// console.log('ST left', body.overlapX, body.deltaX(), 'bt', body.left, tile.right);
if (body.overlapX < 0)
{
process = true;
@@ -963,8 +945,6 @@ Phaser.Physics.Arcade.prototype = {
// RIGHT
body.overlapX = body.right - tile.x;
// console.log('ST right', body.overlapX, body.deltaX(), 'bt', body.right, tile.x);
if (body.overlapX > 0)
{
process = true;
@@ -980,8 +960,6 @@ Phaser.Physics.Arcade.prototype = {
// UP
body.overlapY = body.top - tile.bottom;
// console.log('ST up', body.overlapY, body.deltaY(), 'bt', body.top, tile.bottom);
if (body.overlapY < 0)
{
process = true;
@@ -996,8 +974,6 @@ Phaser.Physics.Arcade.prototype = {
// DOWN
body.overlapY = body.bottom - tile.y;
// console.log('ST down', body.overlapY, body.deltaY(), 'bt', body.bottom, tile.y);
if (body.overlapY > 0)
{
process = true;
@@ -1042,8 +1018,6 @@ Phaser.Physics.Arcade.prototype = {
*/
processTileSeparation: function (body) {
// console.log('PRE processTileSeparation xy', body.x, body.y, 'left', body.left, 'right', body.right, 'up', body.up, 'down', body.down);
if (body.overlapX < 0)
{
body.x -= body.overlapX;
@@ -1052,8 +1026,6 @@ Phaser.Physics.Arcade.prototype = {
body.blocked.x = Math.floor(body.x);
body.blocked.y = Math.floor(body.y);
body.blocked.left = true;
// body.touching.left = true;
// body.touching.none = false;
}
else if (body.overlapX > 0)
{
@@ -1063,8 +1035,6 @@ Phaser.Physics.Arcade.prototype = {
body.blocked.x = Math.floor(body.x);
body.blocked.y = Math.floor(body.y);
body.blocked.right = true;
// body.touching.right = true;
// body.touching.none = false;
}
if (body.overlapY < 0)
@@ -1075,9 +1045,6 @@ Phaser.Physics.Arcade.prototype = {
body.blocked.x = Math.floor(body.x);
body.blocked.y = Math.floor(body.y);
body.blocked.up = true;
// body.touching.up = true;
// body.touching.none = false;
}
else if (body.overlapY > 0)
{
@@ -1087,12 +1054,8 @@ Phaser.Physics.Arcade.prototype = {
body.blocked.x = Math.floor(body.x);
body.blocked.y = Math.floor(body.y);
body.blocked.down = true;
// body.touching.down = true;
// body.touching.none = false;
}
// console.log('POST processTileSeparation xy', body.x, body.y, 'left', body.left, 'right', body.right, 'up', body.up, 'down', body.down);
return true;
},
+10 -94
View File
@@ -393,20 +393,6 @@ Phaser.Physics.Arcade.Body.prototype = {
this.updateScale();
}
if (this.sprite.debug)
{
console.log('Body preUpdate x:', this.x, 'y:', this.y);
// console.log('Body preUpdate Sprite x:', this.sprite.x, 'y:', this.sprite.y);
// console.log('Body preUpdate Sprite world:', this.sprite.world.x, 'y:', this.sprite.world.y);
// console.log('Body preUpdate Sprite position:', this.sprite.position.x, 'y:', this.sprite.position.y);
// console.log('Body preUpdate Sprite localTransform:', this.sprite.localTransform[2], 'y:', this.sprite.localTransform[5]);
// console.log('Body preUpdate Sprite worldTransform:', this.sprite.worldTransform[2], 'y:', this.sprite.worldTransform[5]);
// console.log('Body preUpdate x:', this.x, 'y:', this.y, 'left:', this.left, 'right:', this.right, 'WAS', this.preX, this.preY);
console.log('Body preUpdate blocked:', this.blocked, this.blockFlags);
console.log('Body preUpdate velocity:', this.velocity.x, this.velocity.y);
// console.log('Body preUpdate rotation:', this.rotation, this.preRotation);
}
this.checkBlocked();
this.touching.none = true;
@@ -444,12 +430,6 @@ if (this.sprite.debug)
this.updateBounds();
}
if (this.sprite.debug)
{
console.log('Body preUpdate AFTER integration x:', this.x, 'y:', this.y, 'left:', this.left, 'right:', this.right);
console.log('Body preUpdate velocity:', this.velocity.x, this.velocity.y);
}
},
/**
@@ -462,14 +442,12 @@ if (this.sprite.debug)
if ((this.blocked.left || this.blocked.right) && (Math.floor(this.x) !== this.blocked.x || Math.floor(this.y) !== this.blocked.y))
{
// console.log('resetBlocked unlocked left + right', Math.floor(this.x), this.blocked.x);
this.blocked.left = false;
this.blocked.right = false;
}
if ((this.blocked.up || this.blocked.down) && (Math.floor(this.x) !== this.blocked.x || Math.floor(this.y) !== this.blocked.y))
{
// console.log('resetBlocked unlocked up + down', Math.floor(this.y), this.blocked.y, 'x', Math.floor(this.x), this.blocked.x);
this.blocked.up = false;
this.blocked.down = false;
}
@@ -547,12 +525,6 @@ if (this.sprite.debug)
*/
reboundCheck: function (x, y, rebound) {
if (this.sprite.debug)
{
console.log('reboundCheck start', this.velocity.x, this.velocity.y);
console.log('reBound blocked state', this.blocked);
}
if (x)
{
if (rebound && this.bounce.x !== 0 && (this.blocked.left || this.blocked.right || this.touching.left || this.touching.right))
@@ -562,11 +534,6 @@ if (this.sprite.debug)
{
this.velocity.x *= -this.bounce.x;
this.angle = Math.atan2(this.velocity.y, this.velocity.x);
if (this.sprite.debug)
{
console.log('X rebound applied', this._vx, 'to', this.velocity.x);
}
}
}
@@ -577,16 +544,6 @@ if (this.sprite.debug)
if (((this.blocked.left || this.touching.left) && (gx < 0 || this.velocity.x < 0)) || ((this.blocked.right || this.touching.right) && (gx > 0 || this.velocity.x > 0)))
{
this.velocity.x = 0;
if (this.sprite.debug)
{
console.log('reboundCheck X zeroed');
}
}
if (this.sprite.debug)
{
console.log('reboundCheck X', this.velocity.x, 'gravity', gx);
}
}
}
@@ -600,13 +557,6 @@ if (this.sprite.debug)
{
this.velocity.y *= -this.bounce.y;
this.angle = Math.atan2(this.velocity.y, this.velocity.x);
if (this.sprite.debug)
{
console.log('Y rebound applied', this._vy, 'to', this.velocity.y);
console.log('Y rebound check 1', !(this._vy <= 0 && this.velocity.y > 0));
console.log('Y rebound check 2', !(this._vy >= 0 && this.velocity.y < 0));
}
}
}
@@ -617,16 +567,6 @@ if (this.sprite.debug)
if (((this.blocked.up || this.touching.up) && (gy < 0 || this.velocity.y < 0)) || ((this.blocked.down || this.touching.down) && (gy > 0 || this.velocity.y > 0)))
{
this.velocity.y = 0;
if (this.sprite.debug)
{
console.log('reboundCheck Y zeroed');
}
}
if (this.sprite.debug)
{
console.log('reboundCheck Y', this.velocity.y, 'gravity', gy);
}
}
}
@@ -819,8 +759,16 @@ if (this.sprite.debug)
*/
processRebound: function (body) {
this.velocity.x = body.velocity.x - this.velocity.x * this.bounce.x;
this.velocity.y = body.velocity.y - this.velocity.y * this.bounce.y;
// Don't rebound again if they've already rebounded in this frame
if (!(this._vx <= 0 && this.velocity.x > 0) && !(this._vx >= 0 && this.velocity.x < 0))
{
this.velocity.x = body.velocity.x - this.velocity.x * this.bounce.x;
}
if (!(this._vy <= 0 && this.velocity.y > 0) && !(this._vy >= 0 && this.velocity.y < 0))
{
this.velocity.y = body.velocity.y - this.velocity.y * this.bounce.y;
}
this.angle = Math.atan2(this.velocity.y, this.velocity.x);
@@ -973,12 +921,10 @@ if (this.sprite.debug)
// Which is smaller? Left or Right?
if (this._distances[0] < this._distances[1] && (body.checkCollision.right || this.checkCollision.left))
{
// console.log(this.sprite.name, 'collided on the LEFT with', body.sprite.name, response);
hasSeparated = this.hitLeft(body, response);
}
else if (this._distances[1] < this._distances[0] && (body.checkCollision.left || this.checkCollision.right))
{
// console.log(this.sprite.name, 'collided on the RIGHT with', body.sprite.name, response);
hasSeparated = this.hitRight(body, response);
}
}
@@ -987,19 +933,16 @@ if (this.sprite.debug)
// Which is smaller? Top or Bottom?
if (this._distances[2] < this._distances[3] && (body.checkCollision.down || this.checkCollision.up))
{
// console.log(this.sprite.name, 'collided on the TOP with', body.sprite.name, response);
hasSeparated = this.hitTop(body, response);
}
else if (this._distances[3] < this._distances[2] && (body.checkCollision.up || this.checkCollision.down))
{
// console.log(this.sprite.name, 'collided on the BOTTOM with', body.sprite.name, response);
hasSeparated = this.hitBottom(body, response);
}
}
if (hasSeparated)
{
console.log('Body hasSeparated');
this.game.physics.checkBounds(this);
this.game.physics.checkBounds(body);
}
@@ -1200,11 +1143,6 @@ if (this.sprite.debug)
this._dx = this.game.time.physicsElapsed * (this.velocity.x + this._temp.x / 2);
this._dy = this.game.time.physicsElapsed * (this.velocity.y + this._temp.y / 2);
if (this.sprite.debug)
{
// console.log('integrateVelocity TEMP:', this._temp.x, this._temp.y);
}
// positive = RIGHT / DOWN
// negative = LEFT / UP
@@ -1212,34 +1150,12 @@ if (this.sprite.debug)
{
this.x += this._dx;
this.velocity.x += this._temp.x;
if (this.sprite.debug)
{
// console.log('integrateVelocity x added', this._dx, this.x);
}
}
else
{
if (this.sprite.debug)
{
// console.log('integrateVelocity x failed or zero, blocked left/right', this._dx);
}
}
if ((this._dy < 0 && !this.blocked.up && !this.touching.up) || (this._dy > 0 && !this.blocked.down && !this.touching.down))
{
this.y += this._dy;
this.velocity.y += this._temp.y;
if (this.sprite.debug)
{
// console.log('integrateVelocity y added', this._dy, this.y);
}
}
else
{
if (this.sprite.debug)
{
// console.log('integrateVelocity y failed or zero, blocked up/down', this._dy);
}
}
if (this.velocity.x > this.maxVelocity.x)
+1626
View File
File diff suppressed because it is too large Load Diff