Updated p2.js to latest build.
|
@ -57,6 +57,8 @@ Updates:
|
|||
* Updated display/fullscreen example to reflect new full screen change.
|
||||
* Loads of updates to the TypeScript definitions files - games fully compile now and lots of missing classes added :)
|
||||
* Removed 'null parent' check from Group constructor. Will parent to game.world only if parent value is undefined.
|
||||
* The tutorials have now been translated into Spanish - thanks feiss :)
|
||||
* separateY updated to re-implement the 'riding platforms' special condition (thanks cocoademon)
|
||||
|
||||
|
||||
Bug Fixes:
|
||||
|
|
38
examples/wip/moveToPointer.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
|
||||
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create });
|
||||
|
||||
function preload() {
|
||||
|
||||
game.load.image('arrow', 'assets/sprites/arrow.png');
|
||||
|
||||
}
|
||||
|
||||
var sprite;
|
||||
var tween;
|
||||
|
||||
function create() {
|
||||
|
||||
sprite = game.add.sprite(32, 32, 'arrow');
|
||||
|
||||
sprite.anchor.setTo(0.5, 0.5);
|
||||
|
||||
game.input.onDown.add(moveSprite, this);
|
||||
|
||||
}
|
||||
|
||||
function moveSprite (pointer) {
|
||||
|
||||
if (tween && tween.isRunning)
|
||||
{
|
||||
tween.stop();
|
||||
}
|
||||
|
||||
sprite.rotation = game.physics.angleToPointer(sprite, pointer);
|
||||
|
||||
// 300 = 300 pixels per second = the speed the sprite will move at, regardless of the distance it has to travel
|
||||
var duration = (game.physics.distanceToPointer(sprite, pointer) / 300) * 1000;
|
||||
|
||||
tween = game.add.tween(sprite).to({ x: pointer.x, y: pointer.y }, duration, Phaser.Easing.Linear.None, true);
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
var vec2 = require('../math/vec2')
|
||||
, Nearphase = require('./Nearphase')
|
||||
, Shape = require('./../shapes/Shape')
|
||||
|
||||
module.exports = Broadphase;
|
||||
|
||||
|
@ -10,6 +8,12 @@ module.exports = Broadphase;
|
|||
* @constructor
|
||||
*/
|
||||
function Broadphase(){
|
||||
|
||||
/**
|
||||
* The resulting overlapping pairs. Will be filled with results during .getCollisionPairs().
|
||||
* @property result
|
||||
* @type {Array}
|
||||
*/
|
||||
this.result = [];
|
||||
};
|
||||
|
||||
|
@ -23,10 +27,7 @@ Broadphase.prototype.getCollisionPairs = function(world){
|
|||
throw new Error("getCollisionPairs must be implemented in a subclass!");
|
||||
};
|
||||
|
||||
// Temp things
|
||||
var dist = vec2.create(),
|
||||
worldNormal = vec2.create(),
|
||||
yAxis = vec2.fromValues(0,1);
|
||||
var dist = vec2.create();
|
||||
|
||||
/**
|
||||
* Check whether the bounding radius of two bodies overlap.
|
||||
|
|
|
@ -104,7 +104,7 @@ GridBroadphase.prototype.getCollisionPairs = function(world){
|
|||
}
|
||||
} else if(si instanceof Plane){
|
||||
// Put in all bins for now
|
||||
if(bi.angle === 0){
|
||||
if(bi.angle == 0){
|
||||
var y = bi.position[1];
|
||||
for(var j=0; j!==Nbins && ymin+binsizeY*(j-1)<y; j++){
|
||||
for(var k=0; k<nx; k++){
|
||||
|
|
|
@ -6,8 +6,9 @@ var vec2 = require('../math/vec2')
|
|||
, ContactEquation = require('../constraints/ContactEquation')
|
||||
, FrictionEquation = require('../constraints/FrictionEquation')
|
||||
, Circle = require('../shapes/Circle')
|
||||
, Shape = require('../shapes/Shape')
|
||||
|
||||
module.exports = Nearphase;
|
||||
module.exports = Narrowphase;
|
||||
|
||||
// Temp things
|
||||
var yAxis = vec2.fromValues(0,1);
|
||||
|
@ -27,27 +28,104 @@ var tmp1 = vec2.fromValues(0,0)
|
|||
, tmp13 = vec2.fromValues(0,0)
|
||||
, tmp14 = vec2.fromValues(0,0)
|
||||
, tmp15 = vec2.fromValues(0,0)
|
||||
, tmp16 = vec2.fromValues(0,0)
|
||||
, tmp17 = vec2.fromValues(0,0)
|
||||
, tmp18 = vec2.fromValues(0,0)
|
||||
|
||||
/**
|
||||
* Nearphase. Creates contacts and friction given shapes and transforms.
|
||||
* @class Nearphase
|
||||
* Narrowphase. Creates contacts and friction given shapes and transforms.
|
||||
* @class Narrowphase
|
||||
* @constructor
|
||||
*/
|
||||
function Nearphase(){
|
||||
function Narrowphase(){
|
||||
|
||||
/**
|
||||
* @property contactEquations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.contactEquations = [];
|
||||
|
||||
/**
|
||||
* @property frictionEquations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.frictionEquations = [];
|
||||
|
||||
/**
|
||||
* Whether to make friction equations in the upcoming contacts.
|
||||
* @property enableFriction
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableFriction = true;
|
||||
|
||||
/**
|
||||
* The friction slip force to use when creating friction equations.
|
||||
* @property slipForce
|
||||
* @type {Number}
|
||||
*/
|
||||
this.slipForce = 10.0;
|
||||
|
||||
/**
|
||||
* The friction value to use in the upcoming friction equations.
|
||||
* @property frictionCoefficient
|
||||
* @type {Number}
|
||||
*/
|
||||
this.frictionCoefficient = 0.3;
|
||||
|
||||
this.reuseObjects = true;
|
||||
this.reusableContactEquations = [];
|
||||
this.reusableFrictionEquations = [];
|
||||
|
||||
/**
|
||||
* The restitution value to use in the next contact equations.
|
||||
* @property restitution
|
||||
* @type {Number}
|
||||
*/
|
||||
this.restitution = 0;
|
||||
|
||||
// Keep track of the colliding bodies last step
|
||||
this.collidingBodiesLastStep = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the bodies were in contact since the last reset().
|
||||
* @method collidedLastStep
|
||||
* @param {Body} bi
|
||||
* @param {Body} bj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Narrowphase.prototype.collidedLastStep = function(bi,bj){
|
||||
var id1 = bi.id,
|
||||
id2 = bj.id;
|
||||
if(id1 > id2){
|
||||
var tmp = id1;
|
||||
id1 = id2;
|
||||
id2 = tmp;
|
||||
}
|
||||
return !!this.collidingBodiesLastStep[id1 + " " + id2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws away the old equatons and gets ready to create new
|
||||
* @method reset
|
||||
*/
|
||||
Nearphase.prototype.reset = function(){
|
||||
Narrowphase.prototype.reset = function(){
|
||||
|
||||
// Save the colliding bodies data
|
||||
for(var key in this.collidingBodiesLastStep)
|
||||
delete this.collidingBodiesLastStep[key];
|
||||
for(var i=0; i!==this.contactEquations.length; i++){
|
||||
var eq = this.contactEquations[i],
|
||||
id1 = eq.bi.id,
|
||||
id2 = eq.bj.id;
|
||||
if(id1 > id2){
|
||||
var tmp = id1;
|
||||
id1 = id2;
|
||||
id2 = tmp;
|
||||
}
|
||||
this.collidingBodiesLastStep[id1 + " " + id2] = true;
|
||||
}
|
||||
|
||||
if(this.reuseObjects){
|
||||
var ce = this.contactEquations,
|
||||
fe = this.frictionEquations,
|
||||
|
@ -68,10 +146,14 @@ Nearphase.prototype.reset = function(){
|
|||
* @param {Body} bodyB
|
||||
* @return {ContactEquation}
|
||||
*/
|
||||
Nearphase.prototype.createContactEquation = function(bodyA,bodyB){
|
||||
Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){
|
||||
var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB);
|
||||
c.bi = bodyA;
|
||||
c.bj = bodyB;
|
||||
c.shapeA = shapeA;
|
||||
c.shapeB = shapeB;
|
||||
c.restitution = this.restitution;
|
||||
c.firstImpact = !this.collidedLastStep(bodyA,bodyB);
|
||||
return c;
|
||||
};
|
||||
|
||||
|
@ -82,11 +164,14 @@ Nearphase.prototype.createContactEquation = function(bodyA,bodyB){
|
|||
* @param {Body} bodyB
|
||||
* @return {FrictionEquation}
|
||||
*/
|
||||
Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){
|
||||
Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){
|
||||
var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB);
|
||||
c.bi = bodyA;
|
||||
c.bj = bodyB;
|
||||
c.shapeA = shapeA;
|
||||
c.shapeB = shapeB;
|
||||
c.setSlipForce(this.slipForce);
|
||||
c.frictionCoefficient = this.frictionCoefficient;
|
||||
return c;
|
||||
};
|
||||
|
||||
|
@ -96,16 +181,17 @@ Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){
|
|||
* @param {ContactEquation} contactEquation
|
||||
* @return {FrictionEquation}
|
||||
*/
|
||||
Nearphase.prototype.createFrictionFromContact = function(c){
|
||||
Narrowphase.prototype.createFrictionFromContact = function(c){
|
||||
var eq = this.createFrictionEquation(c.bi,c.bj);
|
||||
vec2.copy(eq.ri, c.ri);
|
||||
vec2.copy(eq.rj, c.rj);
|
||||
vec2.rotate(eq.t, c.ni, -Math.PI / 2);
|
||||
eq.contactEquation = c;
|
||||
return eq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plane/line nearphase
|
||||
* Plane/line Narrowphase
|
||||
* @method planeLine
|
||||
* @param {Body} bi
|
||||
* @param {Plane} si
|
||||
|
@ -116,7 +202,8 @@ Nearphase.prototype.createFrictionFromContact = function(c){
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
|
||||
Narrowphase.prototype[Shape.PLANE | Shape.LINE] =
|
||||
Narrowphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
|
||||
var lineShape = sj,
|
||||
lineAngle = aj,
|
||||
lineBody = bj,
|
||||
|
@ -170,7 +257,7 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
|
|||
|
||||
if(d < 0){
|
||||
|
||||
var c = this.createContactEquation(planeBody,lineBody);
|
||||
var c = this.createContactEquation(planeBody,lineBody,si,sj);
|
||||
|
||||
vec2.copy(c.ni, worldNormal);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
@ -197,12 +284,13 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){
|
|||
}
|
||||
};
|
||||
|
||||
Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
Narrowphase.prototype[Shape.PARTICLE | Shape.CAPSULE] =
|
||||
Narrowphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Circle/line nearphase
|
||||
* Circle/line Narrowphase
|
||||
* @method circleLine
|
||||
* @param {Body} bi
|
||||
* @param {Circle} si
|
||||
|
@ -216,7 +304,8 @@ Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTes
|
|||
* @param {Number} lineRadius Radius to add to the line. Can be used to test Capsules.
|
||||
* @param {Number} circleRadius If set, this value overrides the circle shape radius.
|
||||
*/
|
||||
Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){
|
||||
Narrowphase.prototype[Shape.CIRCLE | Shape.LINE] =
|
||||
Narrowphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){
|
||||
var lineShape = sj,
|
||||
lineAngle = aj,
|
||||
lineBody = bj,
|
||||
|
@ -293,7 +382,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
|
|||
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(circleBody,lineBody);
|
||||
var c = this.createContactEquation(circleBody,lineBody,si,sj);
|
||||
|
||||
vec2.scale(c.ni, orthoDist, -1);
|
||||
vec2.normalize(c.ni, c.ni);
|
||||
|
@ -328,7 +417,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
|
|||
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(circleBody,lineBody);
|
||||
var c = this.createContactEquation(circleBody,lineBody,si,sj);
|
||||
|
||||
vec2.copy(c.ni, dist);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
@ -358,7 +447,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
|
|||
};
|
||||
|
||||
/**
|
||||
* Circle/capsule nearphase
|
||||
* Circle/capsule Narrowphase
|
||||
* @method circleCapsule
|
||||
* @param {Body} bi
|
||||
* @param {Circle} si
|
||||
|
@ -369,12 +458,13 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
Narrowphase.prototype[Shape.CIRCLE | Shape.CAPSULE] =
|
||||
Narrowphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius);
|
||||
};
|
||||
|
||||
/**
|
||||
* Circle/convex nearphase
|
||||
* Circle/convex Narrowphase
|
||||
* @method circleConvex
|
||||
* @param {Body} bi
|
||||
* @param {Circle} si
|
||||
|
@ -385,14 +475,16 @@ Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest)
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
|
||||
Narrowphase.prototype[Shape.CIRCLE | Shape.CONVEX] =
|
||||
Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, circleRadius){
|
||||
var convexShape = sj,
|
||||
convexAngle = aj,
|
||||
convexBody = bj,
|
||||
convexOffset = xj,
|
||||
circleOffset = xi,
|
||||
circleBody = bi,
|
||||
circleShape = si;
|
||||
circleShape = si,
|
||||
circleRadius = typeof(circleRadius)=="number" ? circleRadius : circleShape.radius;
|
||||
|
||||
var worldVertex0 = tmp1,
|
||||
worldVertex1 = tmp2,
|
||||
|
@ -404,14 +496,31 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
|
|||
orthoDist = tmp8,
|
||||
projectedPoint = tmp9,
|
||||
dist = tmp10,
|
||||
worldVertex = tmp11;
|
||||
worldVertex = tmp11,
|
||||
|
||||
closestEdge = -1,
|
||||
closestEdgeDistance = null,
|
||||
closestEdgeOrthoDist = tmp12,
|
||||
closestEdgeProjectedPoint = tmp13,
|
||||
candidate = tmp14,
|
||||
candidateDist = tmp15,
|
||||
minCandidate = tmp16,
|
||||
|
||||
found = false,
|
||||
minCandidateDistance = Number.MAX_VALUE;
|
||||
|
||||
var numReported = 0;
|
||||
|
||||
// New algorithm:
|
||||
// 1. Check so center of circle is not inside the polygon. If it is, this wont work...
|
||||
// 2. For each edge
|
||||
// 2. 1. Get point on circle that is closest to the edge (scale normal with -radius)
|
||||
// 2. 2. Check if point is inside.
|
||||
|
||||
verts = convexShape.vertices;
|
||||
|
||||
// Check all edges first
|
||||
for(var i=0; i<verts.length; i++){
|
||||
for(var i=0; i!==verts.length; i++){
|
||||
var v0 = verts[i],
|
||||
v1 = verts[(i+1)%verts.length];
|
||||
|
||||
|
@ -426,44 +535,135 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
|
|||
// Get tangent to the edge. Points out of the Convex
|
||||
vec2.rotate(worldTangent, worldEdgeUnit, -Math.PI/2);
|
||||
|
||||
// Check distance from the plane spanned by the edge vs the circle
|
||||
sub(dist, circleOffset, worldVertex0);
|
||||
var d = dot(dist, worldTangent);
|
||||
sub(centerDist, worldVertex0, convexOffset);
|
||||
// Get point on circle, closest to the polygon
|
||||
vec2.scale(candidate,worldTangent,-circleShape.radius);
|
||||
add(candidate,candidate,circleOffset);
|
||||
|
||||
sub(convexToCircle, circleOffset, convexOffset);
|
||||
if(pointInConvex(candidate,convexShape,convexOffset,convexAngle)){
|
||||
|
||||
if(d < circleShape.radius && dot(centerDist,convexToCircle) > 0){
|
||||
vec2.sub(candidateDist,worldVertex0,candidate);
|
||||
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent));
|
||||
|
||||
// Now project the circle onto the edge
|
||||
vec2.scale(orthoDist, worldTangent, d);
|
||||
sub(projectedPoint, circleOffset, orthoDist);
|
||||
/*
|
||||
// Check distance from the plane spanned by the edge vs the circle
|
||||
sub(dist, circleOffset, worldVertex0);
|
||||
var d = dot(dist, worldTangent);
|
||||
sub(centerDist, worldVertex0, convexOffset);
|
||||
|
||||
// Check if the point is within the edge span
|
||||
var pos = dot(worldEdgeUnit, projectedPoint);
|
||||
var pos0 = dot(worldEdgeUnit, worldVertex0);
|
||||
var pos1 = dot(worldEdgeUnit, worldVertex1);
|
||||
sub(convexToCircle, circleOffset, convexOffset);
|
||||
|
||||
if(pos > pos0 && pos < pos1){
|
||||
// We got contact!
|
||||
if(d < circleRadius && dot(centerDist,convexToCircle) > 0){
|
||||
|
||||
var c = this.createContactEquation(circleBody,convexBody);
|
||||
// Now project the circle onto the edge
|
||||
vec2.scale(orthoDist, worldTangent, d);
|
||||
sub(projectedPoint, circleOffset, orthoDist);
|
||||
|
||||
vec2.scale(c.ni, orthoDist, -1);
|
||||
vec2.normalize(c.ni, c.ni);
|
||||
|
||||
vec2.scale( c.ri, c.ni, circleShape.radius);
|
||||
// Check if the point is within the edge span
|
||||
var pos = dot(worldEdgeUnit, projectedPoint);
|
||||
var pos0 = dot(worldEdgeUnit, worldVertex0);
|
||||
var pos1 = dot(worldEdgeUnit, worldVertex1);
|
||||
|
||||
if(pos > pos0 && pos < pos1){
|
||||
// We got contact!
|
||||
|
||||
if(justTest) return true;
|
||||
|
||||
if(closestEdgeDistance === null || d*d<closestEdgeDistance*closestEdgeDistance){
|
||||
closestEdgeDistance = d;
|
||||
closestEdge = i;
|
||||
vec2.copy(closestEdgeOrthoDist, orthoDist);
|
||||
vec2.copy(closestEdgeProjectedPoint, projectedPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if(candidateDistance < minCandidateDistance){
|
||||
vec2.copy(minCandidate,candidate);
|
||||
minCandidateDistance = candidateDistance;
|
||||
vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance);
|
||||
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,candidate);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(found){
|
||||
var c = this.createContactEquation(circleBody,convexBody,si,sj);
|
||||
vec2.sub(c.ni, minCandidate, circleOffset)
|
||||
vec2.normalize(c.ni, c.ni);
|
||||
|
||||
vec2.scale(c.ri, c.ni, circleRadius);
|
||||
add(c.ri, c.ri, circleOffset);
|
||||
sub(c.ri, c.ri, circleBody.position);
|
||||
|
||||
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
|
||||
add(c.rj, c.rj, convexOffset);
|
||||
sub(c.rj, c.rj, convexBody.position);
|
||||
|
||||
this.contactEquations.push(c);
|
||||
|
||||
if(this.enableFriction)
|
||||
this.frictionEquations.push( this.createFrictionFromContact(c) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
if(closestEdge != -1){
|
||||
var c = this.createContactEquation(circleBody,convexBody);
|
||||
|
||||
vec2.scale(c.ni, closestEdgeOrthoDist, -1);
|
||||
vec2.normalize(c.ni, c.ni);
|
||||
|
||||
vec2.scale(c.ri, c.ni, circleRadius);
|
||||
add(c.ri, c.ri, circleOffset);
|
||||
sub(c.ri, c.ri, circleBody.position);
|
||||
|
||||
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
|
||||
add(c.rj, c.rj, convexOffset);
|
||||
sub(c.rj, c.rj, convexBody.position);
|
||||
|
||||
this.contactEquations.push(c);
|
||||
|
||||
if(this.enableFriction)
|
||||
this.frictionEquations.push( this.createFrictionFromContact(c) );
|
||||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
// Check all vertices
|
||||
if(circleRadius > 0){
|
||||
for(var i=0; i<verts.length; i++){
|
||||
var localVertex = verts[i];
|
||||
vec2.rotate(worldVertex, localVertex, convexAngle);
|
||||
add(worldVertex, worldVertex, convexOffset);
|
||||
|
||||
sub(dist, worldVertex, circleOffset);
|
||||
if(vec2.squaredLength(dist) < circleRadius*circleRadius){
|
||||
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(circleBody,convexBody,si,sj);
|
||||
|
||||
vec2.copy(c.ni, dist);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
||||
// Vector from circle to contact point is the normal times the circle radius
|
||||
vec2.scale(c.ri, c.ni, circleRadius);
|
||||
add(c.ri, c.ri, circleOffset);
|
||||
sub(c.ri, c.ri, circleBody.position);
|
||||
|
||||
sub( c.rj, projectedPoint, convexOffset);
|
||||
sub(c.rj, worldVertex, convexOffset);
|
||||
add(c.rj, c.rj, convexOffset);
|
||||
sub(c.rj, c.rj, convexBody.position);
|
||||
|
||||
this.contactEquations.push(c);
|
||||
|
||||
if(this.enableFriction){
|
||||
this.frictionEquations.push( this.createFrictionFromContact(c) );
|
||||
this.frictionEquations.push(this.createFrictionFromContact(c));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -471,43 +671,50 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
|
|||
}
|
||||
}
|
||||
|
||||
// Check all vertices
|
||||
for(var i=0; i<verts.length; i++){
|
||||
var localVertex = verts[i];
|
||||
vec2.rotate(worldVertex, localVertex, convexAngle);
|
||||
add(worldVertex, worldVertex, convexOffset);
|
||||
|
||||
sub(dist, worldVertex, circleOffset);
|
||||
if(vec2.squaredLength(dist) < circleShape.radius*circleShape.radius){
|
||||
|
||||
var c = this.createContactEquation(circleBody,convexBody);
|
||||
|
||||
vec2.copy(c.ni, dist);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
||||
// Vector from circle to contact point is the normal times the circle radius
|
||||
vec2.scale(c.ri, c.ni, circleShape.radius);
|
||||
add(c.ri, c.ri, circleOffset);
|
||||
sub(c.ri, c.ri, circleBody.position);
|
||||
|
||||
sub(c.rj, worldVertex, convexOffset);
|
||||
add(c.rj, c.rj, convexOffset);
|
||||
sub(c.rj, c.rj, convexBody.position);
|
||||
|
||||
this.contactEquations.push(c);
|
||||
|
||||
if(this.enableFriction){
|
||||
this.frictionEquations.push(this.createFrictionFromContact(c));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if a point is in a polygon
|
||||
var pic_worldVertex0 = vec2.create(),
|
||||
pic_worldVertex1 = vec2.create(),
|
||||
pic_r0 = vec2.create(),
|
||||
pic_r1 = vec2.create();
|
||||
function pointInConvex(worldPoint,convexShape,convexOffset,convexAngle){
|
||||
var worldVertex0 = pic_worldVertex0,
|
||||
worldVertex1 = pic_worldVertex1,
|
||||
r0 = pic_r0,
|
||||
r1 = pic_r1,
|
||||
point = worldPoint,
|
||||
verts = convexShape.vertices,
|
||||
lastCross = null;
|
||||
for(var i=0; i!==verts.length+1; i++){
|
||||
var v0 = verts[i%verts.length],
|
||||
v1 = verts[(i+1)%verts.length];
|
||||
|
||||
// Transform vertices to world
|
||||
// can we instead transform point to local of the convex???
|
||||
vec2.rotate(worldVertex0, v0, convexAngle);
|
||||
vec2.rotate(worldVertex1, v1, convexAngle);
|
||||
add(worldVertex0, worldVertex0, convexOffset);
|
||||
add(worldVertex1, worldVertex1, convexOffset);
|
||||
|
||||
sub(r0, worldVertex0, point);
|
||||
sub(r1, worldVertex1, point);
|
||||
var cross = vec2.crossLength(r0,r1);
|
||||
|
||||
if(lastCross===null) lastCross = cross;
|
||||
|
||||
// If we got a different sign of the distance vector, the point is out of the polygon
|
||||
if(cross*lastCross <= 0){
|
||||
return false;
|
||||
}
|
||||
lastCross = cross;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Particle/convex nearphase
|
||||
* Particle/convex Narrowphase
|
||||
* @method particleConvex
|
||||
* @param {Body} bi
|
||||
* @param {Particle} si
|
||||
|
@ -517,8 +724,10 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){
|
|||
* @param {Convex} sj
|
||||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
* @todo use pointInConvex and code more similar to circleConvex
|
||||
*/
|
||||
Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
Narrowphase.prototype[Shape.PARTICLE | Shape.CONVEX] =
|
||||
Narrowphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
var convexShape = sj,
|
||||
convexAngle = aj,
|
||||
convexBody = bj,
|
||||
|
@ -540,15 +749,26 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
|
|||
closestEdge = -1,
|
||||
closestEdgeDistance = null,
|
||||
closestEdgeOrthoDist = tmp12,
|
||||
closestEdgeProjectedPoint = tmp13;
|
||||
closestEdgeProjectedPoint = tmp13,
|
||||
r0 = tmp14, // vector from particle to vertex0
|
||||
r1 = tmp15,
|
||||
localPoint = tmp16,
|
||||
candidateDist = tmp17,
|
||||
minEdgeNormal = tmp18,
|
||||
minCandidateDistance = Number.MAX_VALUE;
|
||||
|
||||
var numReported = 0;
|
||||
var numReported = 0,
|
||||
found = false,
|
||||
verts = convexShape.vertices;
|
||||
|
||||
verts = convexShape.vertices;
|
||||
// Check if the particle is in the polygon at all
|
||||
if(!pointInConvex(particleOffset,convexShape,convexOffset,convexAngle))
|
||||
return false;
|
||||
|
||||
// Check all edges first
|
||||
for(var i=0; i<verts.length; i++){
|
||||
var v0 = verts[i],
|
||||
// Check edges first
|
||||
var lastCross = null;
|
||||
for(var i=0; i!==verts.length+1; i++){
|
||||
var v0 = verts[i%verts.length],
|
||||
v1 = verts[(i+1)%verts.length];
|
||||
|
||||
// Transform vertices to world
|
||||
|
@ -571,6 +791,8 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
|
|||
|
||||
sub(convexToparticle, particleOffset, convexOffset);
|
||||
|
||||
|
||||
/*
|
||||
if(d < 0 && dot(centerDist,convexToparticle) >= 0){
|
||||
|
||||
// Now project the particle onto the edge
|
||||
|
@ -594,18 +816,32 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
vec2.sub(candidateDist,worldVertex0,particleOffset);
|
||||
var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent));
|
||||
|
||||
if(candidateDistance < minCandidateDistance){
|
||||
minCandidateDistance = candidateDistance;
|
||||
vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance);
|
||||
vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,particleOffset);
|
||||
vec2.copy(minEdgeNormal,worldTangent);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(closestEdge != -1){
|
||||
var c = this.createContactEquation(particleBody,convexBody);
|
||||
if(found){
|
||||
var c = this.createContactEquation(particleBody,convexBody,si,sj);
|
||||
|
||||
vec2.copy(c.ni, closestEdgeOrthoDist);
|
||||
vec2.scale(c.ni, minEdgeNormal, -1);
|
||||
vec2.normalize(c.ni, c.ni);
|
||||
|
||||
// Particle has no extent to the contact point
|
||||
vec2.set(c.ri, 0, 0);
|
||||
add(c.ri, c.ri, particleOffset);
|
||||
sub(c.ri, c.ri, particleBody.position);
|
||||
|
||||
// From convex center to point
|
||||
sub(c.rj, closestEdgeProjectedPoint, convexOffset);
|
||||
add(c.rj, c.rj, convexOffset);
|
||||
sub(c.rj, c.rj, convexBody.position);
|
||||
|
@ -623,7 +859,7 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
|
|||
};
|
||||
|
||||
/**
|
||||
* Circle/circle nearphase
|
||||
* Circle/circle Narrowphase
|
||||
* @method circleCircle
|
||||
* @param {Body} bi
|
||||
* @param {Circle} si
|
||||
|
@ -634,7 +870,8 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
Narrowphase.prototype[Shape.CIRCLE] =
|
||||
Narrowphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){
|
||||
var bodyA = bi,
|
||||
shapeA = si,
|
||||
offsetA = xi,
|
||||
|
@ -651,7 +888,7 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
|
|||
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(bodyA,bodyB);
|
||||
var c = this.createContactEquation(bodyA,bodyB,si,sj);
|
||||
sub(c.ni, offsetB, offsetA);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
||||
|
@ -673,26 +910,27 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
|
|||
};
|
||||
|
||||
/**
|
||||
* Convex/Plane nearphase
|
||||
* @method convexPlane
|
||||
* Plane/Convex Narrowphase
|
||||
* @method planeConvex
|
||||
* @param {Body} bi
|
||||
* @param {Convex} si
|
||||
* @param {Plane} si
|
||||
* @param {Array} xi
|
||||
* @param {Number} ai
|
||||
* @param {Body} bj
|
||||
* @param {Plane} sj
|
||||
* @param {Convex} sj
|
||||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
var convexBody = bi,
|
||||
convexOffset = xi,
|
||||
convexShape = si,
|
||||
convexAngle = ai,
|
||||
planeBody = bj,
|
||||
planeShape = sj,
|
||||
planeOffset = xj,
|
||||
planeAngle = aj;
|
||||
Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] =
|
||||
Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
var convexBody = bj,
|
||||
convexOffset = xj,
|
||||
convexShape = sj,
|
||||
convexAngle = aj,
|
||||
planeBody = bi,
|
||||
planeShape = si,
|
||||
planeOffset = xi,
|
||||
planeAngle = ai;
|
||||
|
||||
var worldVertex = tmp1,
|
||||
worldNormal = tmp2,
|
||||
|
@ -701,8 +939,8 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
var numReported = 0;
|
||||
vec2.rotate(worldNormal, yAxis, planeAngle);
|
||||
|
||||
for(var i=0; i<si.vertices.length; i++){
|
||||
var v = si.vertices[i];
|
||||
for(var i=0; i<convexShape.vertices.length; i++){
|
||||
var v = convexShape.vertices[i];
|
||||
vec2.rotate(worldVertex, v, convexAngle);
|
||||
add(worldVertex, worldVertex, convexOffset);
|
||||
|
||||
|
@ -713,7 +951,7 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
// Found vertex
|
||||
numReported++;
|
||||
|
||||
var c = this.createContactEquation(planeBody,convexBody);
|
||||
var c = this.createContactEquation(planeBody,convexBody,planeShape,convexShape);
|
||||
|
||||
sub(dist, worldVertex, planeOffset);
|
||||
|
||||
|
@ -746,7 +984,16 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
};
|
||||
|
||||
/**
|
||||
* Nearphase for particle vs plane
|
||||
* @method convexPlane
|
||||
* @deprecated Use .planeConvex() instead!
|
||||
*/
|
||||
Narrowphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
console.warn("Narrowphase.prototype.convexPlane is deprecated. Use planeConvex instead!");
|
||||
return this.planeConvex( bj,sj,xj,aj, bi,si,xi,ai );
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrowphase for particle vs plane
|
||||
* @method particlePlane
|
||||
* @param {Body} bi The particle body
|
||||
* @param {Particle} si Particle shape
|
||||
|
@ -757,7 +1004,8 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
* @param {Array} xj World position for the plane
|
||||
* @param {Number} aj World angle for the plane
|
||||
*/
|
||||
Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
Narrowphase.prototype[Shape.PARTICLE | Shape.PLANE] =
|
||||
Narrowphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
var particleBody = bi,
|
||||
particleShape = si,
|
||||
particleOffset = xi,
|
||||
|
@ -779,7 +1027,7 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
|
|||
if(d > 0) return false;
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(planeBody,particleBody);
|
||||
var c = this.createContactEquation(planeBody,particleBody,sj,si);
|
||||
|
||||
vec2.copy(c.ni, worldNormal);
|
||||
vec2.scale( dist, c.ni, d );
|
||||
|
@ -801,7 +1049,7 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
|
|||
};
|
||||
|
||||
/**
|
||||
* Circle/Particle nearphase
|
||||
* Circle/Particle Narrowphase
|
||||
* @method circleParticle
|
||||
* @param {Body} bi
|
||||
* @param {Circle} si
|
||||
|
@ -812,7 +1060,8 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] =
|
||||
Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){
|
||||
var circleBody = bi,
|
||||
circleShape = si,
|
||||
circleOffset = xi,
|
||||
|
@ -825,7 +1074,7 @@ Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justT
|
|||
if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return false;
|
||||
if(justTest) return true;
|
||||
|
||||
var c = this.createContactEquation(circleBody,particleBody);
|
||||
var c = this.createContactEquation(circleBody,particleBody,si,sj);
|
||||
vec2.copy(c.ni, dist);
|
||||
vec2.normalize(c.ni,c.ni);
|
||||
|
||||
|
@ -850,28 +1099,39 @@ var capsulePlane_tmpCircle = new Circle(1),
|
|||
capsulePlane_tmp1 = vec2.create(),
|
||||
capsulePlane_tmp2 = vec2.create(),
|
||||
capsulePlane_tmp3 = vec2.create();
|
||||
Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
|
||||
Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] =
|
||||
Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
var end1 = capsulePlane_tmp1,
|
||||
end2 = capsulePlane_tmp2,
|
||||
circle = capsulePlane_tmpCircle,
|
||||
dst = capsulePlane_tmp3;
|
||||
|
||||
// Compute world end positions
|
||||
vec2.set(end1, -si.length/2, 0);
|
||||
vec2.rotate(end1,end1,ai);
|
||||
add(end1,end1,xi);
|
||||
vec2.set(end1, -sj.length/2, 0);
|
||||
vec2.rotate(end1,end1,aj);
|
||||
add(end1,end1,xj);
|
||||
|
||||
vec2.set(end2, si.length/2, 0);
|
||||
vec2.rotate(end2,end2,ai);
|
||||
add(end2,end2,xi);
|
||||
vec2.set(end2, sj.length/2, 0);
|
||||
vec2.rotate(end2,end2,aj);
|
||||
add(end2,end2,xj);
|
||||
|
||||
circle.radius = si.radius;
|
||||
circle.radius = sj.radius;
|
||||
|
||||
// Do nearphase as two circles
|
||||
this.circlePlane(bi,circle,end1,0, bj,sj,xj,aj);
|
||||
this.circlePlane(bi,circle,end2,0, bj,sj,xj,aj);
|
||||
// Do Narrowphase as two circles
|
||||
this.circlePlane(bj,circle,end1,0, bi,si,xi,ai);
|
||||
this.circlePlane(bj,circle,end2,0, bi,si,xi,ai);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method capsulePlane
|
||||
* @deprecated Use .planeCapsule() instead!
|
||||
*/
|
||||
Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!");
|
||||
return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ContactEquations and FrictionEquations for a collision.
|
||||
* @method circlePlane
|
||||
|
@ -883,7 +1143,8 @@ Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
* @param {Array} xj Extra offset for the plane shape.
|
||||
* @param {Number} aj Extra angle to apply to the plane
|
||||
*/
|
||||
Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] =
|
||||
Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
var circleBody = bi,
|
||||
circleShape = si,
|
||||
circleOffset = xi, // Offset from body center, rotated!
|
||||
|
@ -910,7 +1171,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
if(d > circleShape.radius) return false; // No overlap. Abort.
|
||||
|
||||
// Create contact
|
||||
var contact = this.createContactEquation(planeBody,circleBody);
|
||||
var contact = this.createContactEquation(planeBody,circleBody,sj,si);
|
||||
|
||||
// ni is the plane world normal
|
||||
vec2.copy(contact.ni, worldNormal);
|
||||
|
@ -937,7 +1198,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
|
||||
|
||||
/**
|
||||
* Convex/convex nearphase.See <a href="http://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/">this article</a> for more info.
|
||||
* Convex/convex Narrowphase.See <a href="http://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/">this article</a> for more info.
|
||||
* @method convexConvex
|
||||
* @param {Body} bi
|
||||
* @param {Convex} si
|
||||
|
@ -948,7 +1209,8 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
* @param {Array} xj
|
||||
* @param {Number} aj
|
||||
*/
|
||||
Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
Narrowphase.prototype[Shape.CONVEX] =
|
||||
Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
||||
var sepAxis = tmp1,
|
||||
worldPoint = tmp2,
|
||||
worldPoint0 = tmp3,
|
||||
|
@ -959,7 +1221,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
dist = tmp8,
|
||||
worldNormal = tmp9;
|
||||
|
||||
var found = Nearphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis);
|
||||
var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis);
|
||||
if(!found) return false;
|
||||
|
||||
// Make sure the separating axis is directed from shape i to shape j
|
||||
|
@ -969,8 +1231,8 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
}
|
||||
|
||||
// Find edges with normals closest to the separating axis
|
||||
var closestEdge1 = Nearphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis
|
||||
closestEdge2 = Nearphase.getClosestEdge(sj,aj,sepAxis);
|
||||
var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis
|
||||
closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis);
|
||||
|
||||
if(closestEdge1==-1 || closestEdge2==-1) return false;
|
||||
|
||||
|
@ -1036,7 +1298,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){
|
|||
// Project it to the center edge and use the projection direction as normal
|
||||
|
||||
// Create contact
|
||||
var c = this.createContactEquation(bodyA,bodyB);
|
||||
var c = this.createContactEquation(bodyA,bodyB,si,sj);
|
||||
|
||||
// Get center edge from body A
|
||||
var v0 = shapeA.vertices[(closestEdgeA) % shapeA.vertices.length],
|
||||
|
@ -1090,7 +1352,7 @@ var pcoa_tmp1 = vec2.fromValues(0,0);
|
|||
* @param {Array} worldAxis
|
||||
* @param {Array} result
|
||||
*/
|
||||
Nearphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){
|
||||
Narrowphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){
|
||||
var max=null,
|
||||
min=null,
|
||||
v,
|
||||
|
@ -1141,7 +1403,7 @@ var fsa_tmp1 = vec2.fromValues(0,0)
|
|||
* @param {Array} sepAxis The resulting axis
|
||||
* @return {Boolean} Whether the axis could be found.
|
||||
*/
|
||||
Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){
|
||||
Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){
|
||||
var maxDist = null,
|
||||
overlap = false,
|
||||
found = false,
|
||||
|
@ -1172,8 +1434,8 @@ Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepA
|
|||
vec2.normalize(normal,normal);
|
||||
|
||||
// Project hulls onto that normal
|
||||
Nearphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1);
|
||||
Nearphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2);
|
||||
Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1);
|
||||
Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2);
|
||||
|
||||
// Order by span position
|
||||
var a=span1,
|
||||
|
@ -1215,7 +1477,7 @@ var gce_tmp1 = vec2.fromValues(0,0)
|
|||
* @param {Boolean} flip
|
||||
* @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed.
|
||||
*/
|
||||
Nearphase.getClosestEdge = function(c,angle,axis,flip){
|
||||
Narrowphase.getClosestEdge = function(c,angle,axis,flip){
|
||||
var localAxis = gce_tmp1,
|
||||
edge = gce_tmp2,
|
||||
normal = gce_tmp3;
|
|
@ -27,10 +27,10 @@ function QuadTree(bounds, pointQuad, maxDepth, maxChildren){
|
|||
}
|
||||
|
||||
/**
|
||||
* The root node of the QuadTree which covers the entire area being segmented.
|
||||
* @property root
|
||||
* @type Node
|
||||
*/
|
||||
* The root node of the QuadTree which covers the entire area being segmented.
|
||||
* @property root
|
||||
* @type Node
|
||||
*/
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
|
@ -299,7 +299,7 @@ BoundsNode.prototype.insert = function(item){
|
|||
console.log("radius:",item.boundingRadius);
|
||||
console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width);
|
||||
console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height);
|
||||
*/
|
||||
*/
|
||||
|
||||
//todo: make _bounds bounds
|
||||
if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child
|
||||
|
|
|
@ -19,24 +19,24 @@ function SAP1DBroadphase(world){
|
|||
Broadphase.apply(this);
|
||||
|
||||
/**
|
||||
* List of bodies currently in the broadphase.
|
||||
* @property axisList
|
||||
* @type {Array}
|
||||
*/
|
||||
* List of bodies currently in the broadphase.
|
||||
* @property axisList
|
||||
* @type {Array}
|
||||
*/
|
||||
this.axisList = world.bodies.slice(0);
|
||||
|
||||
/**
|
||||
* The world to search in.
|
||||
* @property world
|
||||
* @type {World}
|
||||
*/
|
||||
* The world to search in.
|
||||
* @property world
|
||||
* @type {World}
|
||||
*/
|
||||
this.world = world;
|
||||
|
||||
/**
|
||||
* Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on.
|
||||
* @property axisIndex
|
||||
* @type {Number}
|
||||
*/
|
||||
* Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on.
|
||||
* @property axisIndex
|
||||
* @type {Number}
|
||||
*/
|
||||
this.axisIndex = 0;
|
||||
|
||||
// Add listeners to update the list of bodies.
|
||||
|
@ -92,23 +92,13 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){
|
|||
|
||||
// Look through the list
|
||||
for(i=0, N=bodies.length; i!==N; i++){
|
||||
var bi = bodies[i],
|
||||
biPos = bi.position[axisIndex],
|
||||
ri = bi.boundingRadius;
|
||||
var bi = bodies[i];
|
||||
|
||||
for(j=i+1; j<N; j++){
|
||||
var bj = bodies[j],
|
||||
bjPos = bj.position[axisIndex],
|
||||
rj = bj.boundingRadius,
|
||||
boundA1 = biPos-ri,
|
||||
boundA2 = biPos+ri,
|
||||
boundB1 = bjPos-rj,
|
||||
boundB2 = bjPos+rj;
|
||||
var bj = bodies[j];
|
||||
|
||||
// Abort if we got gap til the next body
|
||||
if( boundB1 > boundA2 ){
|
||||
if(!SAP1DBroadphase.checkBounds(bi,bj,axisIndex))
|
||||
break;
|
||||
}
|
||||
|
||||
// If we got overlap, add pair
|
||||
if(Broadphase.boundingRadiusCheck(bi,bj))
|
||||
|
@ -118,3 +108,25 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){
|
|||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the bounds of two bodies overlap, along the given SAP axis.
|
||||
* @static
|
||||
* @method checkBounds
|
||||
* @param {Body} bi
|
||||
* @param {Body} bj
|
||||
* @param {Number} axisIndex
|
||||
* @return {Boolean}
|
||||
*/
|
||||
SAP1DBroadphase.checkBounds = function(bi,bj,axisIndex){
|
||||
var biPos = bi.position[axisIndex],
|
||||
ri = bi.boundingRadius,
|
||||
bjPos = bj.position[axisIndex],
|
||||
rj = bj.boundingRadius,
|
||||
boundA1 = biPos-ri,
|
||||
boundA2 = biPos+ri,
|
||||
boundB1 = bjPos-rj,
|
||||
boundB2 = bjPos+rj;
|
||||
|
||||
return boundB1 < boundA2;
|
||||
};
|
||||
|
|
|
@ -12,24 +12,24 @@ module.exports = Constraint;
|
|||
function Constraint(bodyA,bodyB){
|
||||
|
||||
/**
|
||||
* Equations to be solved in this constraint
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
* Equations to be solved in this constraint
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.equations = [];
|
||||
|
||||
/**
|
||||
* First body participating in the constraint.
|
||||
* @property bodyA
|
||||
* @type {Body}
|
||||
*/
|
||||
* First body participating in the constraint.
|
||||
* @property bodyA
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bodyA = bodyA;
|
||||
|
||||
/**
|
||||
* Second body participating in the constraint.
|
||||
* @property bodyB
|
||||
* @type {Body}
|
||||
*/
|
||||
* Second body participating in the constraint.
|
||||
* @property bodyB
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bodyB = bodyB;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ var Equation = require("./Equation"),
|
|||
module.exports = ContactEquation;
|
||||
|
||||
/**
|
||||
* Non-penetration constraint equation.
|
||||
* Non-penetration constraint equation. Tries to make the ri and rj vectors the same point.
|
||||
*
|
||||
* @class ContactEquation
|
||||
* @constructor
|
||||
|
@ -14,13 +14,57 @@ module.exports = ContactEquation;
|
|||
* @param {Body} bj
|
||||
*/
|
||||
function ContactEquation(bi,bj){
|
||||
Equation.call(this,bi,bj,0,1e6);
|
||||
Equation.call(this,bi,bj,0,Number.MAX_VALUE);
|
||||
|
||||
/**
|
||||
* Vector from body i center of mass to the contact point.
|
||||
* @property ri
|
||||
* @type {Array}
|
||||
*/
|
||||
this.ri = vec2.create();
|
||||
this.penetrationVec = vec2.create();
|
||||
|
||||
/**
|
||||
* Vector from body j center of mass to the contact point.
|
||||
* @property rj
|
||||
* @type {Array}
|
||||
*/
|
||||
this.rj = vec2.create();
|
||||
|
||||
/**
|
||||
* The normal vector, pointing out of body i
|
||||
* @property ni
|
||||
* @type {Array}
|
||||
*/
|
||||
this.ni = vec2.create();
|
||||
this.rixn = 0;
|
||||
this.rjxn = 0;
|
||||
|
||||
/**
|
||||
* The restitution to use. 0=no bounciness, 1=max bounciness.
|
||||
* @property restitution
|
||||
* @type {Number}
|
||||
*/
|
||||
this.restitution = 0;
|
||||
|
||||
/**
|
||||
* Set to true if this is the first impact between the bodies (not persistant contact).
|
||||
* @property firstImpact
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.firstImpact = false;
|
||||
|
||||
/**
|
||||
* The shape in body i that triggered this contact.
|
||||
* @property shapeA
|
||||
* @type {Shape}
|
||||
*/
|
||||
this.shapeA = null;
|
||||
|
||||
/**
|
||||
* The shape in body j that triggered this contact.
|
||||
* @property shapeB
|
||||
* @type {Shape}
|
||||
*/
|
||||
this.shapeB = null;
|
||||
};
|
||||
ContactEquation.prototype = new Equation();
|
||||
ContactEquation.prototype.constructor = ContactEquation;
|
||||
|
@ -32,105 +76,39 @@ ContactEquation.prototype.computeB = function(a,b,h){
|
|||
xi = bi.position,
|
||||
xj = bj.position;
|
||||
|
||||
var vi = bi.velocity,
|
||||
wi = bi.angularVelocity,
|
||||
fi = bi.force,
|
||||
taui = bi.angularForce;
|
||||
|
||||
var vj = bj.velocity,
|
||||
wj = bj.angularVelocity,
|
||||
fj = bj.force,
|
||||
tauj = bj.angularForce;
|
||||
|
||||
var penetrationVec = this.penetrationVec,
|
||||
invMassi = bi.invMass,
|
||||
invMassj = bj.invMass,
|
||||
invIi = bi.invInertia,
|
||||
invIj = bj.invInertia,
|
||||
n = this.ni;
|
||||
n = this.ni,
|
||||
G = this.G;
|
||||
|
||||
// Caluclate cross products
|
||||
this.rixn = vec2.crossLength(ri,n);
|
||||
this.rjxn = vec2.crossLength(rj,n);
|
||||
var rixn = vec2.crossLength(ri,n),
|
||||
rjxn = vec2.crossLength(rj,n);
|
||||
|
||||
// G = [-n -rixn n rjxn]
|
||||
G[0] = -n[0];
|
||||
G[1] = -n[1];
|
||||
G[2] = -rixn;
|
||||
G[3] = n[0];
|
||||
G[4] = n[1];
|
||||
G[5] = rjxn;
|
||||
|
||||
// Calculate q = xj+rj -(xi+ri) i.e. the penetration vector
|
||||
vec2.add(penetrationVec,xj,rj);
|
||||
vec2.sub(penetrationVec,penetrationVec,xi);
|
||||
vec2.sub(penetrationVec,penetrationVec,ri);
|
||||
|
||||
var Gq = vec2.dot(n,penetrationVec);
|
||||
|
||||
// Compute iteration
|
||||
var GW = vec2.dot(vj,n) - vec2.dot(vi,n) + wj * this.rjxn - wi * this.rixn;
|
||||
var GiMf = vec2.dot(fj,n)*invMassj - vec2.dot(fi,n)*invMassi + invIj*tauj*this.rjxn - invIi*taui*this.rixn;
|
||||
var GW, Gq;
|
||||
if(this.firstImpact && this.restitution !== 0){
|
||||
Gq = 0;
|
||||
GW = (1/b)*(1+this.restitution) * this.computeGW();
|
||||
} else {
|
||||
GW = this.computeGW();
|
||||
Gq = vec2.dot(n,penetrationVec);
|
||||
}
|
||||
|
||||
var GiMf = this.computeGiMf();
|
||||
var B = - Gq * a - GW * b - h*GiMf;
|
||||
|
||||
return B;
|
||||
};
|
||||
|
||||
// Compute C = GMG+eps in the SPOOK equation
|
||||
var computeC_tmp1 = vec2.create(),
|
||||
tmpMat1 = mat2.create(),
|
||||
tmpMat2 = mat2.create();
|
||||
ContactEquation.prototype.computeC = function(eps){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
n = this.ni,
|
||||
rixn = this.rixn,
|
||||
rjxn = this.rjxn,
|
||||
tmp = computeC_tmp1,
|
||||
imMat1 = tmpMat1,
|
||||
imMat2 = tmpMat2;
|
||||
|
||||
mat2.identity(imMat1);
|
||||
mat2.identity(imMat2);
|
||||
imMat1[0] = imMat1[3] = bi.invMass;
|
||||
imMat2[0] = imMat2[3] = bj.invMass;
|
||||
|
||||
var C = vec2.dot(n,vec2.transformMat2(tmp,n,imMat1)) + vec2.dot(n,vec2.transformMat2(tmp,n,imMat2)) + eps;
|
||||
//var C = bi.invMass + bj.invMass + eps;
|
||||
|
||||
C += bi.invInertia * this.rixn * this.rixn;
|
||||
C += bj.invInertia * this.rjxn * this.rjxn;
|
||||
|
||||
return C;
|
||||
};
|
||||
|
||||
ContactEquation.prototype.computeGWlambda = function(){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
n = this.ni,
|
||||
dot = vec2.dot;
|
||||
|
||||
return dot(n, bj.vlambda) + bj.wlambda * this.rjxn - dot(n, bi.vlambda) - bi.wlambda * this.rixn;
|
||||
};
|
||||
|
||||
var addToWlambda_temp = vec2.create();
|
||||
ContactEquation.prototype.addToWlambda = function(deltalambda){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
n = this.ni,
|
||||
temp = addToWlambda_temp,
|
||||
imMat1 = tmpMat1,
|
||||
imMat2 = tmpMat2;
|
||||
|
||||
mat2.identity(imMat1);
|
||||
mat2.identity(imMat2);
|
||||
imMat1[0] = imMat1[3] = bi.invMass;
|
||||
imMat2[0] = imMat2[3] = bj.invMass;
|
||||
|
||||
// Add to linear velocity
|
||||
//vec2.scale(temp,n,-bi.invMass*deltalambda);
|
||||
vec2.scale(temp,vec2.transformMat2(temp,n,imMat1),-deltalambda);
|
||||
vec2.add( bi.vlambda,bi.vlambda, temp );
|
||||
|
||||
//vec2.scale(temp,n,bj.invMass*deltalambda);
|
||||
vec2.scale(temp,vec2.transformMat2(temp,n,imMat2),deltalambda);
|
||||
vec2.add( bj.vlambda,bj.vlambda, temp);
|
||||
|
||||
// Add to angular velocity
|
||||
bi.wlambda -= bi.invInertia * this.rixn * deltalambda;
|
||||
bj.wlambda += bj.invInertia * this.rjxn * deltalambda;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var Constraint = require('./Constraint')
|
||||
, ContactEquation = require('./ContactEquation')
|
||||
, Equation = require('./Equation')
|
||||
, vec2 = require('../math/vec2')
|
||||
|
||||
module.exports = DistanceConstraint;
|
||||
|
@ -19,16 +19,25 @@ module.exports = DistanceConstraint;
|
|||
function DistanceConstraint(bodyA,bodyB,distance,maxForce){
|
||||
Constraint.call(this,bodyA,bodyB);
|
||||
|
||||
/**
|
||||
* The distance to keep.
|
||||
* @property distance
|
||||
* @type {Number}
|
||||
*/
|
||||
this.distance = distance;
|
||||
|
||||
if(typeof(maxForce)==="undefined" ) {
|
||||
maxForce = 1e6;
|
||||
}
|
||||
|
||||
var normal = new ContactEquation(bodyA,bodyB); // Just in the normal direction
|
||||
if(typeof(maxForce)==="undefined" )
|
||||
maxForce = Number.MAX_VALUE;
|
||||
|
||||
var normal = new Equation(bodyA,bodyB,-maxForce,maxForce); // Just in the normal direction
|
||||
this.equations = [ normal ];
|
||||
|
||||
var r = vec2.create();
|
||||
normal.computeGq = function(){
|
||||
vec2.sub(r, bodyB.position, bodyA.position);
|
||||
return vec2.length(r)-distance;
|
||||
};
|
||||
|
||||
// Make the contact constraint bilateral
|
||||
this.setMaxForce(maxForce);
|
||||
}
|
||||
|
@ -38,24 +47,38 @@ DistanceConstraint.prototype = new Constraint();
|
|||
* Update the constraint equations. Should be done if any of the bodies changed position, before solving.
|
||||
* @method update
|
||||
*/
|
||||
var n = vec2.create();
|
||||
DistanceConstraint.prototype.update = function(){
|
||||
var normal = this.equations[0],
|
||||
bodyA = this.bodyA,
|
||||
bodyB = this.bodyB,
|
||||
distance = this.distance;
|
||||
distance = this.distance,
|
||||
G = normal.G;
|
||||
|
||||
vec2.sub(normal.ni, bodyB.position, bodyA.position);
|
||||
vec2.normalize(normal.ni,normal.ni);
|
||||
vec2.scale(normal.ri, normal.ni, distance*0.5);
|
||||
vec2.scale(normal.rj, normal.ni, -distance*0.5);
|
||||
vec2.sub(n, bodyB.position, bodyA.position);
|
||||
vec2.normalize(n,n);
|
||||
G[0] = -n[0];
|
||||
G[1] = -n[1];
|
||||
G[3] = n[0];
|
||||
G[4] = n[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the max force to be used
|
||||
* @method setMaxForce
|
||||
* @param {Number} f
|
||||
*/
|
||||
DistanceConstraint.prototype.setMaxForce = function(f){
|
||||
var normal = this.equations[0];
|
||||
normal.minForce = -f;
|
||||
normal.maxForce = f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the max force
|
||||
* @method getMaxForce
|
||||
* @return {Number}
|
||||
*/
|
||||
DistanceConstraint.prototype.getMaxForce = function(f){
|
||||
var normal = this.equations[0];
|
||||
return normal.maxForce;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
module.exports = Equation;
|
||||
|
||||
var vec2 = require('../math/vec2'),
|
||||
mat2 = require('../math/mat2'),
|
||||
Utils = require('../utils/Utils');
|
||||
|
||||
/**
|
||||
* Base class for constraint equations.
|
||||
* @class Equation
|
||||
|
@ -12,52 +16,75 @@ module.exports = Equation;
|
|||
function Equation(bi,bj,minForce,maxForce){
|
||||
|
||||
/**
|
||||
* Minimum force to apply when solving
|
||||
* @property minForce
|
||||
* @type {Number}
|
||||
*/
|
||||
* Minimum force to apply when solving
|
||||
* @property minForce
|
||||
* @type {Number}
|
||||
*/
|
||||
this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce;
|
||||
|
||||
/**
|
||||
* Max force to apply when solving
|
||||
* @property maxForce
|
||||
* @type {Number}
|
||||
*/
|
||||
* Max force to apply when solving
|
||||
* @property maxForce
|
||||
* @type {Number}
|
||||
*/
|
||||
this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce;
|
||||
|
||||
/**
|
||||
* First body participating in the constraint
|
||||
* @property bi
|
||||
* @type {Body}
|
||||
*/
|
||||
* First body participating in the constraint
|
||||
* @property bi
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bi = bi;
|
||||
|
||||
/**
|
||||
* Second body participating in the constraint
|
||||
* @property bj
|
||||
* @type {Body}
|
||||
*/
|
||||
* Second body participating in the constraint
|
||||
* @property bj
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bj = bj;
|
||||
|
||||
/**
|
||||
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
* The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation.
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
this.stiffness = 1e6;
|
||||
|
||||
/**
|
||||
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
* The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps.
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
this.relaxation = 4;
|
||||
|
||||
/**
|
||||
* The Jacobian entry of this equation. 6 numbers, 3 per body (x,y,angle).
|
||||
* @property G
|
||||
* @type {Array}
|
||||
*/
|
||||
this.G = new Utils.ARRAY_TYPE(6);
|
||||
|
||||
// Constraint frames for body i and j
|
||||
/*
|
||||
this.xi = vec2.create();
|
||||
this.xj = vec2.create();
|
||||
this.ai = 0;
|
||||
this.aj = 0;
|
||||
*/
|
||||
this.offset = 0;
|
||||
|
||||
this.a = 0;
|
||||
this.b = 0;
|
||||
this.eps = 0;
|
||||
this.h = 0;
|
||||
this.updateSpookParams(1/60);
|
||||
|
||||
/**
|
||||
* The resulting constraint multiplier from the last solve. This is mostly equivalent to the force produced by the constraint.
|
||||
* @property multiplier
|
||||
* @type {Number}
|
||||
*/
|
||||
this.multiplier = 0;
|
||||
};
|
||||
Equation.prototype.constructor = Equation;
|
||||
|
||||
|
@ -75,3 +102,215 @@ Equation.prototype.updateSpookParams = function(timeStep){
|
|||
this.eps = 4.0 / (h * h * k * (1 + 4 * d));
|
||||
this.h = timeStep;
|
||||
};
|
||||
|
||||
function Gmult(G,vi,wi,vj,wj){
|
||||
return G[0] * vi[0] +
|
||||
G[1] * vi[1] +
|
||||
G[2] * wi +
|
||||
G[3] * vj[0] +
|
||||
G[4] * vj[1] +
|
||||
G[5] * wj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the RHS of the SPOOK equation
|
||||
* @method computeB
|
||||
* @return {Number}
|
||||
*/
|
||||
Equation.prototype.computeB = function(a,b,h){
|
||||
var GW = this.computeGW();
|
||||
var Gq = this.computeGq();
|
||||
var GiMf = this.computeGiMf();
|
||||
return - Gq * a - GW * b - GiMf*h;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes G*q, where q are the generalized body coordinates
|
||||
* @method computeGq
|
||||
* @return {Number}
|
||||
*/
|
||||
var qi = vec2.create(),
|
||||
qj = vec2.create();
|
||||
Equation.prototype.computeGq = function(){
|
||||
var G = this.G,
|
||||
bi = this.bi,
|
||||
bj = this.bj,
|
||||
xi = bi.position,
|
||||
xj = bj.position,
|
||||
ai = bi.angle,
|
||||
aj = bj.angle;
|
||||
|
||||
// Transform to the given body frames
|
||||
/*
|
||||
vec2.rotate(qi,this.xi,ai);
|
||||
vec2.rotate(qj,this.xj,aj);
|
||||
vec2.add(qi,qi,xi);
|
||||
vec2.add(qj,qj,xj);
|
||||
*/
|
||||
|
||||
return Gmult(G, qi, ai, qj, aj) + this.offset;
|
||||
};
|
||||
|
||||
var tmp_i = vec2.create(),
|
||||
tmp_j = vec2.create();
|
||||
Equation.prototype.transformedGmult = function(G,vi,wi,vj,wj){
|
||||
// Transform velocity to the given body frames
|
||||
// v_p = v + w x r
|
||||
/*
|
||||
vec2.rotate(tmp_i,this.xi,Math.PI / 2 + this.bi.angle); // Get r, and rotate 90 degrees. We get the "x r" part
|
||||
vec2.rotate(tmp_j,this.xj,Math.PI / 2 + this.bj.angle);
|
||||
vec2.scale(tmp_i,tmp_i,wi); // Temp vectors are now (w x r)
|
||||
vec2.scale(tmp_j,tmp_j,wj);
|
||||
vec2.add(tmp_i,tmp_i,vi);
|
||||
vec2.add(tmp_j,tmp_j,vj);
|
||||
*/
|
||||
|
||||
// Note: angular velocity is same
|
||||
return Gmult(G,vi,wi,vj,wj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes G*W, where W are the body velocities
|
||||
* @method computeGW
|
||||
* @return {Number}
|
||||
*/
|
||||
Equation.prototype.computeGW = function(){
|
||||
var G = this.G,
|
||||
bi = this.bi,
|
||||
bj = this.bj,
|
||||
vi = bi.velocity,
|
||||
vj = bj.velocity,
|
||||
wi = bi.angularVelocity,
|
||||
wj = bj.angularVelocity;
|
||||
return this.transformedGmult(G,vi,wi,vj,wj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes G*Wlambda, where W are the body velocities
|
||||
* @method computeGWlambda
|
||||
* @return {Number}
|
||||
*/
|
||||
Equation.prototype.computeGWlambda = function(){
|
||||
var G = this.G,
|
||||
bi = this.bi,
|
||||
bj = this.bj,
|
||||
vi = bi.vlambda,
|
||||
vj = bj.vlambda,
|
||||
wi = bi.wlambda,
|
||||
wj = bj.wlambda;
|
||||
return this.transformedGmult(G,vi,wi,vj,wj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes G*inv(M)*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies.
|
||||
* @method computeGiMf
|
||||
* @return {Number}
|
||||
*/
|
||||
var iMfi = vec2.create(),
|
||||
iMfj = vec2.create();
|
||||
Equation.prototype.computeGiMf = function(){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
fi = bi.force,
|
||||
ti = bi.angularForce,
|
||||
fj = bj.force,
|
||||
tj = bj.angularForce,
|
||||
invMassi = bi.invMass,
|
||||
invMassj = bj.invMass,
|
||||
invIi = bi.invInertia,
|
||||
invIj = bj.invInertia,
|
||||
G = this.G;
|
||||
|
||||
vec2.scale(iMfi, fi,invMassi);
|
||||
vec2.scale(iMfj, fj,invMassj);
|
||||
|
||||
return this.transformedGmult(G,iMfi,ti*invIi,iMfj,tj*invIj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes G*inv(M)*G'
|
||||
* @method computeGiMGt
|
||||
* @return {Number}
|
||||
*/
|
||||
Equation.prototype.computeGiMGt = function(){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
invMassi = bi.invMass,
|
||||
invMassj = bj.invMass,
|
||||
invIi = bi.invInertia,
|
||||
invIj = bj.invInertia,
|
||||
G = this.G;
|
||||
|
||||
return G[0] * G[0] * invMassi +
|
||||
G[1] * G[1] * invMassi +
|
||||
G[2] * G[2] * invIi +
|
||||
G[3] * G[3] * invMassj +
|
||||
G[4] * G[4] * invMassj +
|
||||
G[5] * G[5] * invIj;
|
||||
};
|
||||
|
||||
var addToWlambda_temp = vec2.create(),
|
||||
addToWlambda_Gi = vec2.create(),
|
||||
addToWlambda_Gj = vec2.create(),
|
||||
addToWlambda_ri = vec2.create(),
|
||||
addToWlambda_rj = vec2.create();
|
||||
var tmpMat1 = mat2.create(),
|
||||
tmpMat2 = mat2.create();
|
||||
|
||||
/**
|
||||
* Add constraint velocity to the bodies.
|
||||
* @method addToWlambda
|
||||
* @param {Number} deltalambda
|
||||
*/
|
||||
Equation.prototype.addToWlambda = function(deltalambda){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
temp = addToWlambda_temp,
|
||||
imMat1 = tmpMat1,
|
||||
imMat2 = tmpMat2,
|
||||
Gi = addToWlambda_Gi,
|
||||
Gj = addToWlambda_Gj,
|
||||
ri = addToWlambda_ri,
|
||||
rj = addToWlambda_rj,
|
||||
G = this.G;
|
||||
|
||||
Gi[0] = G[0];
|
||||
Gi[1] = G[1];
|
||||
Gj[0] = G[3];
|
||||
Gj[1] = G[4];
|
||||
|
||||
mat2.identity(imMat1);
|
||||
mat2.identity(imMat2);
|
||||
imMat1[0] = imMat1[3] = bi.invMass;
|
||||
imMat2[0] = imMat2[3] = bj.invMass;
|
||||
|
||||
/*
|
||||
vec2.rotate(ri,this.xi,bi.angle);
|
||||
vec2.rotate(rj,this.xj,bj.angle);
|
||||
*/
|
||||
|
||||
// Add to linear velocity
|
||||
vec2.scale(temp,vec2.transformMat2(temp,Gi,imMat1),deltalambda);
|
||||
vec2.add( bi.vlambda, bi.vlambda, temp);
|
||||
// This impulse is in the offset frame
|
||||
// Also add contribution to angular
|
||||
//bi.wlambda -= vec2.crossLength(temp,ri);
|
||||
|
||||
vec2.scale(temp,vec2.transformMat2(temp,Gj,imMat2),deltalambda);
|
||||
vec2.add( bj.vlambda, bj.vlambda, temp);
|
||||
//bj.wlambda -= vec2.crossLength(temp,rj);
|
||||
|
||||
// Add to angular velocity
|
||||
bi.wlambda += bi.invInertia * G[2] * deltalambda;
|
||||
bj.wlambda += bj.invInertia * G[5] * deltalambda;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps
|
||||
* @method computeC
|
||||
* @param {Number} eps
|
||||
* @return {Number}
|
||||
*/
|
||||
Equation.prototype.computeC = function(eps){
|
||||
return this.computeGiMGt() + eps;
|
||||
};
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
var mat2 = require('../math/mat2')
|
||||
, vec2 = require('../math/vec2')
|
||||
, Equation = require('./Equation')
|
||||
, Utils = require('../utils/Utils')
|
||||
|
||||
module.exports = FrictionEquation;
|
||||
|
||||
// 3D cross product from glmatrix, until we get this to work...
|
||||
function cross(out, a, b) {
|
||||
var ax = a[0], ay = a[1], az = a[2],
|
||||
bx = b[0], by = b[1], bz = b[2];
|
||||
|
||||
out[0] = ay * bz - az * by;
|
||||
out[1] = az * bx - ax * bz;
|
||||
out[2] = ax * by - ay * bx;
|
||||
return out;
|
||||
};
|
||||
|
||||
var dot = vec2.dot;
|
||||
|
||||
/**
|
||||
* Constrains the slipping in a contact along a tangent
|
||||
*
|
||||
|
@ -31,28 +19,53 @@ function FrictionEquation(bi,bj,slipForce){
|
|||
Equation.call(this,bi,bj,-slipForce,slipForce);
|
||||
|
||||
/**
|
||||
* Relative vector from center of body i to the contact point, in world coords.
|
||||
* @property ri
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* Relative vector from center of body i to the contact point, in world coords.
|
||||
* @property ri
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.ri = vec2.create();
|
||||
|
||||
/**
|
||||
* Relative vector from center of body j to the contact point, in world coords.
|
||||
* @property rj
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* Relative vector from center of body j to the contact point, in world coords.
|
||||
* @property rj
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.rj = vec2.create();
|
||||
|
||||
/**
|
||||
* Tangent vector that the friction force will act along, in world coords.
|
||||
* @property t
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* Tangent vector that the friction force will act along, in world coords.
|
||||
* @property t
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.t = vec2.create();
|
||||
|
||||
this.rixt = 0;
|
||||
this.rjxt = 0;
|
||||
/**
|
||||
* A ContactEquation connected to this friction. The contact equation can be used to rescale the max force for the friction.
|
||||
* @property contactEquation
|
||||
* @type {ContactEquation}
|
||||
*/
|
||||
this.contactEquation = null;
|
||||
|
||||
/**
|
||||
* The shape in body i that triggered this friction.
|
||||
* @property shapeA
|
||||
* @type {Shape}
|
||||
*/
|
||||
this.shapeA = null;
|
||||
|
||||
/**
|
||||
* The shape in body j that triggered this friction.
|
||||
* @property shapeB
|
||||
* @type {Shape}
|
||||
*/
|
||||
this.shapeB = null;
|
||||
|
||||
/**
|
||||
* The friction coefficient to use.
|
||||
* @property frictionCoefficient
|
||||
* @type {Number}
|
||||
*/
|
||||
this.frictionCoefficient = 0.3;
|
||||
};
|
||||
FrictionEquation.prototype = new Equation();
|
||||
FrictionEquation.prototype.constructor = FrictionEquation;
|
||||
|
@ -62,119 +75,34 @@ FrictionEquation.prototype.constructor = FrictionEquation;
|
|||
* larger than this value.
|
||||
* @method setSlipForce
|
||||
* @param {Number} slipForce
|
||||
* @deprecated Use .frictionCoefficient instead
|
||||
*/
|
||||
FrictionEquation.prototype.setSlipForce = function(slipForce){
|
||||
this.maxForce = slipForce;
|
||||
this.minForce = -slipForce;
|
||||
};
|
||||
|
||||
var rixtVec = [0,0,0];
|
||||
var rjxtVec = [0,0,0];
|
||||
var ri3 = [0,0,0];
|
||||
var rj3 = [0,0,0];
|
||||
var t3 = [0,0,0];
|
||||
FrictionEquation.prototype.computeB = function(a,b,h){
|
||||
var a = this.a,
|
||||
b = this.b,
|
||||
bi = this.bi,
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
ri = this.ri,
|
||||
rj = this.rj,
|
||||
t = this.t;
|
||||
t = this.t,
|
||||
G = this.G;
|
||||
|
||||
// Caluclate cross products
|
||||
ri3[0] = ri[0];
|
||||
ri3[1] = ri[1];
|
||||
rj3[0] = rj[0];
|
||||
rj3[1] = rj[1];
|
||||
t3[0] = t[0];
|
||||
t3[1] = t[1];
|
||||
cross(rixtVec, ri3, t3);//ri.cross(t,rixt);
|
||||
cross(rjxtVec, rj3, t3);//rj.cross(t,rjxt);
|
||||
this.rixt = rixtVec[2];
|
||||
this.rjxt = rjxtVec[2];
|
||||
// G = [-t -rixt t rjxt]
|
||||
// And remember, this is a pure velocity constraint, g is always zero!
|
||||
G[0] = -t[0];
|
||||
G[1] = -t[1];
|
||||
G[2] = -vec2.crossLength(ri,t);
|
||||
G[3] = t[0];
|
||||
G[4] = t[1];
|
||||
G[5] = vec2.crossLength(rj,t);
|
||||
|
||||
var GW = -dot(bi.velocity,t) + dot(bj.velocity,t) - this.rixt*bi.angularVelocity + this.rjxt*bj.angularVelocity; // eq. 40
|
||||
var GiMf = -dot(bi.force,t)*bi.invMass +dot(bj.force,t)*bj.invMass -this.rixt*bi.invInertia*bi.angularForce + this.rjxt*bj.invInertia*bj.angularForce;
|
||||
var GW = this.computeGW();
|
||||
var GiMf = this.computeGiMf();
|
||||
|
||||
var B = /* - Gq * a */ - GW * b - h*GiMf;
|
||||
var B = /* - g * a */ - GW * b - h*GiMf;
|
||||
|
||||
return B;
|
||||
};
|
||||
|
||||
// Compute C = G * iM * G' + eps
|
||||
//
|
||||
// G*iM*G' =
|
||||
//
|
||||
// [ iM1 ] [-t ]
|
||||
// [-t (-ri x t) t (rj x t)] * [ iI1 ] [-ri x t]
|
||||
// [ iM2 ] [t ]
|
||||
// [ iI2 ] [rj x t ]
|
||||
//
|
||||
// = (-t)*iM1*(-t) + (-ri x t)*iI1*(-ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
|
||||
//
|
||||
// = t*iM1*t + (ri x t)*iI1*(ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t)
|
||||
//
|
||||
var computeC_tmp1 = vec2.create(),
|
||||
tmpMat1 = mat2.create(),
|
||||
tmpMat2 = mat2.create();
|
||||
FrictionEquation.prototype.computeC = function(eps){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
t = this.t,
|
||||
C = 0.0,
|
||||
tmp = computeC_tmp1,
|
||||
imMat1 = tmpMat1,
|
||||
imMat2 = tmpMat2,
|
||||
dot = vec2.dot;
|
||||
|
||||
mat2.identity(imMat1);
|
||||
mat2.identity(imMat2);
|
||||
|
||||
imMat1[0] = imMat1[3] = bi.invMass;
|
||||
imMat2[0] = imMat2[3] = bj.invMass;
|
||||
|
||||
C = dot(t,vec2.transformMat2(tmp,t,imMat1)) + dot(t,vec2.transformMat2(tmp,t,imMat2)) + eps;
|
||||
|
||||
//C = bi.invMass + bj.invMass + eps;
|
||||
|
||||
C += bi.invInertia * this.rixt * this.rixt;
|
||||
C += bj.invInertia * this.rjxt * this.rjxt;
|
||||
|
||||
return C;
|
||||
};
|
||||
|
||||
FrictionEquation.prototype.computeGWlambda = function(){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
t = this.t,
|
||||
dot = vec2.dot;
|
||||
|
||||
return dot(t, bj.vlambda) + bj.wlambda * this.rjxt - bi.wlambda * this.rixt - dot(t, bi.vlambda);
|
||||
};
|
||||
|
||||
var FrictionEquation_addToWlambda_tmp = vec2.create();
|
||||
FrictionEquation.prototype.addToWlambda = function(deltalambda){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
t = this.t,
|
||||
tmp = FrictionEquation_addToWlambda_tmp,
|
||||
imMat1 = tmpMat1,
|
||||
imMat2 = tmpMat2;
|
||||
|
||||
mat2.identity(imMat1);
|
||||
mat2.identity(imMat2);
|
||||
imMat1[0] = imMat1[3] = bi.invMass;
|
||||
imMat2[0] = imMat2[3] = bj.invMass;
|
||||
|
||||
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat1),-deltalambda);
|
||||
//vec2.scale(tmp, t, -bi.invMass * deltalambda); //t.mult(invMassi * deltalambda, tmp);
|
||||
vec2.add(bi.vlambda, bi.vlambda, tmp); //bi.vlambda.vsub(tmp,bi.vlambda);
|
||||
|
||||
vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat2),deltalambda);
|
||||
//vec2.scale(tmp, t, bj.invMass * deltalambda); //t.mult(invMassj * deltalambda, tmp);
|
||||
vec2.add(bj.vlambda, bj.vlambda, tmp); //bj.vlambda.vadd(tmp,bj.vlambda);
|
||||
|
||||
bi.wlambda -= bi.invInertia * this.rixt * deltalambda;
|
||||
bj.wlambda += bj.invInertia * this.rjxt * deltalambda;
|
||||
};
|
||||
|
|
123
src/physics/advanced/constraints/LockConstraint.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
var Constraint = require('./Constraint')
|
||||
, vec2 = require('../math/vec2')
|
||||
, Equation = require('./Equation')
|
||||
|
||||
module.exports = LockConstraint;
|
||||
|
||||
/**
|
||||
* Locks the relative position between two bodies.
|
||||
*
|
||||
* @class LockConstraint
|
||||
* @constructor
|
||||
* @author schteppe
|
||||
* @param {Body} bodyA
|
||||
* @param {Body} bodyB
|
||||
* @param {Object} [options]
|
||||
* @param {Array} [options.localOffsetB] The offset of bodyB in bodyA's frame.
|
||||
* @param {number} [options.localAngleB] The angle of bodyB in bodyA's frame.
|
||||
* @param {number} [options.maxForce]
|
||||
* @extends {Constraint}
|
||||
*/
|
||||
function LockConstraint(bodyA,bodyB,options){
|
||||
Constraint.call(this,bodyA,bodyB);
|
||||
var maxForce = ( typeof(options.maxForce)=="undefined" ? Number.MAX_VALUE : options.maxForce );
|
||||
var localOffsetB = options.localOffsetB || vec2.fromValues(0,0);
|
||||
localOffsetB = vec2.fromValues(localOffsetB[0],localOffsetB[1]);
|
||||
|
||||
var localAngleB = options.localAngleB || 0;
|
||||
|
||||
// Use 3 equations:
|
||||
// gx = (xj - xi - l) * xhat = 0
|
||||
// gy = (xj - xi - l) * yhat = 0
|
||||
// gr = (xi - xj + r) * that = 0
|
||||
//
|
||||
// ...where:
|
||||
// l is the localOffsetB vector rotated to world in bodyA frame
|
||||
// r is the same vector but reversed and rotated from bodyB frame
|
||||
// xhat, yhat are world axis vectors
|
||||
// that is the tangent of r
|
||||
//
|
||||
// For the first two constraints, we get
|
||||
// G*W = (vj - vi - ldot ) * xhat
|
||||
// = (vj - vi - wi x l) * xhat
|
||||
//
|
||||
// Since (wi x l) * xhat = (l x xhat) * wi, we get
|
||||
// G*W = [ -1 0 (-l x xhat) 1 0 0] * [vi wi vj wj]
|
||||
//
|
||||
// The last constraint gives
|
||||
// GW = (vi - vj + wj x r) * that
|
||||
// = [ that 0 -that (r x t) ]
|
||||
|
||||
var x = new Equation(bodyA,bodyB,-maxForce,maxForce),
|
||||
y = new Equation(bodyA,bodyB,-maxForce,maxForce),
|
||||
rot = new Equation(bodyA,bodyB,-maxForce,maxForce);
|
||||
|
||||
var l = vec2.create(),
|
||||
g = vec2.create();
|
||||
x.computeGq = function(){
|
||||
vec2.rotate(l,localOffsetB,bodyA.angle);
|
||||
vec2.sub(g,bodyB.position,bodyA.position);
|
||||
vec2.sub(g,g,l);
|
||||
return g[0];
|
||||
}
|
||||
y.computeGq = function(){
|
||||
vec2.rotate(l,localOffsetB,bodyA.angle);
|
||||
vec2.sub(g,bodyB.position,bodyA.position);
|
||||
vec2.sub(g,g,l);
|
||||
return g[1];
|
||||
};
|
||||
var r = vec2.create(),
|
||||
t = vec2.create();
|
||||
rot.computeGq = function(){
|
||||
vec2.rotate(r,localOffsetB,bodyB.angle - localAngleB);
|
||||
vec2.scale(r,r,-1);
|
||||
vec2.sub(g,bodyA.position,bodyB.position);
|
||||
vec2.add(g,g,r);
|
||||
vec2.rotate(t,r,-Math.PI/2);
|
||||
vec2.normalize(t,t);
|
||||
return vec2.dot(g,t);
|
||||
};
|
||||
|
||||
this.localOffsetB = localOffsetB;
|
||||
this.localAngleB = localAngleB;
|
||||
this.maxForce = maxForce;
|
||||
|
||||
var eqs = this.equations = [ x, y, rot ];
|
||||
}
|
||||
LockConstraint.prototype = new Constraint();
|
||||
|
||||
var l = vec2.create();
|
||||
var r = vec2.create();
|
||||
var t = vec2.create();
|
||||
var xAxis = vec2.fromValues(1,0);
|
||||
var yAxis = vec2.fromValues(0,1);
|
||||
LockConstraint.prototype.update = function(){
|
||||
var x = this.equations[0],
|
||||
y = this.equations[1],
|
||||
rot = this.equations[2],
|
||||
bodyA = this.bodyA,
|
||||
bodyB = this.bodyB;
|
||||
|
||||
vec2.rotate(l,this.localOffsetB,bodyA.angle);
|
||||
vec2.rotate(r,this.localOffsetB,bodyB.angle - this.localAngleB);
|
||||
vec2.scale(r,r,-1);
|
||||
|
||||
vec2.rotate(t,r,Math.PI/2);
|
||||
vec2.normalize(t,t);
|
||||
|
||||
x.G[0] = -1;
|
||||
x.G[1] = 0;
|
||||
x.G[2] = -vec2.crossLength(l,xAxis);
|
||||
x.G[3] = 1;
|
||||
|
||||
y.G[0] = 0;
|
||||
y.G[1] = -1;
|
||||
y.G[2] = -vec2.crossLength(l,yAxis);
|
||||
y.G[4] = 1;
|
||||
|
||||
rot.G[0] = -t[0];
|
||||
rot.G[1] = -t[1];
|
||||
rot.G[3] = t[0];
|
||||
rot.G[4] = t[1];
|
||||
rot.G[5] = vec2.crossLength(r,t);
|
||||
};
|
|
@ -1,94 +0,0 @@
|
|||
var Constraint = require('./Constraint')
|
||||
, ContactEquation = require('./ContactEquation')
|
||||
, RotationalVelocityEquation = require('./RotationalVelocityEquation')
|
||||
, vec2 = require('../math/vec2')
|
||||
|
||||
module.exports = PointToPointConstraint;
|
||||
|
||||
/**
|
||||
* Connects two bodies at given offset points
|
||||
* @class PointToPointConstraint
|
||||
* @constructor
|
||||
* @author schteppe
|
||||
* @param {Body} bodyA
|
||||
* @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to.
|
||||
* @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point.
|
||||
* @param {Float32Array} pivotB See pivotA.
|
||||
* @param {Number} maxForce The maximum force that should be applied to constrain the bodies.
|
||||
* @extends {Constraint}
|
||||
* @todo Ability to specify world points
|
||||
*/
|
||||
function PointToPointConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
|
||||
Constraint.call(this,bodyA,bodyB);
|
||||
|
||||
maxForce = typeof(maxForce)!="undefined" ? maxForce : 1e7;
|
||||
|
||||
this.pivotA = pivotA;
|
||||
this.pivotB = pivotB;
|
||||
|
||||
// Equations to be fed to the solver
|
||||
var eqs = this.equations = [
|
||||
new ContactEquation(bodyA,bodyB), // Normal
|
||||
new ContactEquation(bodyA,bodyB), // Tangent
|
||||
];
|
||||
|
||||
var normal = eqs[0];
|
||||
var tangent = eqs[1];
|
||||
|
||||
tangent.minForce = normal.minForce = -maxForce;
|
||||
tangent.maxForce = normal.maxForce = maxForce;
|
||||
|
||||
this.motorEquation = null;
|
||||
}
|
||||
PointToPointConstraint.prototype = new Constraint();
|
||||
|
||||
PointToPointConstraint.prototype.update = function(){
|
||||
var bodyA = this.bodyA,
|
||||
bodyB = this.bodyB,
|
||||
pivotA = this.pivotA,
|
||||
pivotB = this.pivotB,
|
||||
eqs = this.equations,
|
||||
normal = eqs[0],
|
||||
tangent= eqs[1];
|
||||
|
||||
vec2.subtract(normal.ni, bodyB.position, bodyA.position);
|
||||
vec2.normalize(normal.ni,normal.ni);
|
||||
vec2.rotate(normal.ri, pivotA, bodyA.angle);
|
||||
vec2.rotate(normal.rj, pivotB, bodyB.angle);
|
||||
|
||||
vec2.rotate(tangent.ni, normal.ni, Math.PI / 2);
|
||||
vec2.copy(tangent.ri, normal.ri);
|
||||
vec2.copy(tangent.rj, normal.rj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable the rotational motor
|
||||
* @method enableMotor
|
||||
*/
|
||||
PointToPointConstraint.prototype.enableMotor = function(){
|
||||
if(this.motorEquation) return;
|
||||
this.motorEquation = new RotationalVelocityEquation(this.bodyA,this.bodyB);
|
||||
this.equations.push(this.motorEquation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable the rotational motor
|
||||
* @method disableMotor
|
||||
*/
|
||||
PointToPointConstraint.prototype.disableMotor = function(){
|
||||
if(!this.motorEquation) return;
|
||||
var i = this.equations.indexOf(this.motorEquation);
|
||||
this.motorEquation = null;
|
||||
this.equations.splice(i,1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the speed of the rotational constraint motor
|
||||
* @method setMotorSpeed
|
||||
* @param {Number} speed
|
||||
*/
|
||||
PointToPointConstraint.prototype.setMotorSpeed = function(speed){
|
||||
if(!this.motorEquation) return;
|
||||
var i = this.equations.indexOf(this.motorEquation);
|
||||
this.equations[i].relativeVelocity = speed;
|
||||
};
|
|
@ -1,58 +1,107 @@
|
|||
var Constraint = require('./Constraint')
|
||||
, ContactEquation = require('./ContactEquation')
|
||||
, Equation = require('./Equation')
|
||||
, vec2 = require('../math/vec2')
|
||||
, RotationalLockEquation = require('./RotationalLockEquation')
|
||||
|
||||
module.exports = PrismaticConstraint;
|
||||
|
||||
/**
|
||||
* Constraint that only allows translation along a line between the bodies, no rotation
|
||||
* Constraint that only allows bodies to move along a line, relative to each other. See <a href="http://www.iforce2d.net/b2dtut/joints-prismatic">this tutorial</a>.
|
||||
*
|
||||
* @class PrismaticConstraint
|
||||
* @constructor
|
||||
* @extends {Constraint}
|
||||
* @author schteppe
|
||||
* @param {Body} bodyA
|
||||
* @param {Body} bodyB
|
||||
* @param {Object} options
|
||||
* @param {Number} options.maxForce
|
||||
* @param {Array} options.worldAxis
|
||||
* @param {Array} options.localAxisA
|
||||
* @param {Array} options.localAxisB
|
||||
* @extends {Constraint}
|
||||
* @param {Number} options.maxForce Max force to be applied by the constraint
|
||||
* @param {Array} options.localAnchorA Body A's anchor point, defined in its own local frame.
|
||||
* @param {Array} options.localAnchorB Body B's anchor point, defined in its own local frame.
|
||||
* @param {Array} options.localAxisA An axis, defined in body A frame, that body B's anchor point may slide along.
|
||||
*/
|
||||
function PrismaticConstraint(bodyA,bodyB,options){
|
||||
options = options || {};
|
||||
Constraint.call(this,bodyA,bodyB);
|
||||
|
||||
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : 1e6;
|
||||
// Get anchors
|
||||
var localAnchorA = vec2.fromValues(0,0),
|
||||
localAxisA = vec2.fromValues(1,0),
|
||||
localAnchorB = vec2.fromValues(0,0);
|
||||
if(options.localAnchorA) vec2.copy(localAnchorA, options.localAnchorA);
|
||||
if(options.localAxisA) vec2.copy(localAxisA, options.localAxisA);
|
||||
if(options.localAnchorB) vec2.copy(localAnchorB, options.localAnchorB);
|
||||
|
||||
// Equations to be fed to the solver
|
||||
var eqs = this.equations = [
|
||||
new ContactEquation(bodyA,bodyB), // Tangent for bodyA
|
||||
new ContactEquation(bodyB,bodyA), // Tangent for bodyB
|
||||
];
|
||||
/**
|
||||
* @property localAnchorA
|
||||
* @type {Array}
|
||||
*/
|
||||
this.localAnchorA = localAnchorA;
|
||||
|
||||
var tangentA = eqs[0],
|
||||
tangentB = eqs[1];
|
||||
/**
|
||||
* @property localAnchorB
|
||||
* @type {Array}
|
||||
*/
|
||||
this.localAnchorB = localAnchorB;
|
||||
|
||||
tangentA.minForce = tangentB.minForce = -maxForce;
|
||||
tangentA.maxForce = tangentB.maxForce = maxForce;
|
||||
/**
|
||||
* @property localAxisA
|
||||
* @type {Array}
|
||||
*/
|
||||
this.localAxisA = localAxisA;
|
||||
|
||||
var worldAxis = vec2.create();
|
||||
if(options.worldAxis){
|
||||
vec2.copy(worldAxis, options.worldAxis);
|
||||
} else {
|
||||
vec2.sub(worldAxis, bodyB.position, bodyA.position);
|
||||
/*
|
||||
|
||||
The constraint violation for the common axis point is
|
||||
|
||||
g = ( xj + rj - xi - ri ) * t := gg*t
|
||||
|
||||
where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame.
|
||||
|
||||
gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t )
|
||||
|
||||
Note the use of the chain rule. Now we identify the jacobian
|
||||
|
||||
G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj]
|
||||
|
||||
The rotational part is just a rotation lock.
|
||||
|
||||
*/
|
||||
|
||||
var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : Number.MAX_VALUE;
|
||||
|
||||
// Translational part
|
||||
var trans = new Equation(bodyA,bodyB,-maxForce,maxForce);
|
||||
var ri = new vec2.create(),
|
||||
rj = new vec2.create(),
|
||||
gg = new vec2.create(),
|
||||
t = new vec2.create();
|
||||
trans.computeGq = function(){
|
||||
// g = ( xj + rj - xi - ri ) * t
|
||||
return vec2.dot(gg,t);
|
||||
};
|
||||
trans.update = function(){
|
||||
var G = this.G,
|
||||
xi = bodyA.position,
|
||||
xj = bodyB.position;
|
||||
vec2.rotate(ri,localAnchorA,bodyA.angle);
|
||||
vec2.rotate(rj,localAnchorB,bodyB.angle);
|
||||
vec2.add(gg,xj,rj);
|
||||
vec2.sub(gg,gg,xi);
|
||||
vec2.sub(gg,gg,ri);
|
||||
vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2);
|
||||
|
||||
G[0] = -t[0];
|
||||
G[1] = -t[1];
|
||||
G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg);
|
||||
G[3] = t[0];
|
||||
G[4] = t[1];
|
||||
G[5] = vec2.crossLength(rj,t);
|
||||
}
|
||||
vec2.normalize(worldAxis,worldAxis);
|
||||
var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce);
|
||||
|
||||
// Axis that is local in each body
|
||||
this.localAxisA = vec2.create();
|
||||
this.localAxisB = vec2.create();
|
||||
if(options.localAxisA) vec2.copy(this.localAxisA, options.localAxisA);
|
||||
else vec2.rotate(this.localAxisA, worldAxis, -bodyA.angle);
|
||||
|
||||
if(options.localAxisB) vec2.copy(this.localAxisB, options.localAxisB);
|
||||
else vec2.rotate(this.localAxisB, worldAxis, -bodyB.angle);
|
||||
this.equations.push(trans,rot);
|
||||
}
|
||||
|
||||
PrismaticConstraint.prototype = new Constraint();
|
||||
|
@ -62,22 +111,7 @@ PrismaticConstraint.prototype = new Constraint();
|
|||
* @method update
|
||||
*/
|
||||
PrismaticConstraint.prototype.update = function(){
|
||||
var tangentA = this.equations[0],
|
||||
tangentB = this.equations[1],
|
||||
bodyA = this.bodyA,
|
||||
bodyB = this.bodyB;
|
||||
|
||||
// Get tangent directions
|
||||
vec2.rotate(tangentA.ni, this.localAxisA, bodyA.angle - Math.PI/2);
|
||||
vec2.rotate(tangentB.ni, this.localAxisB, bodyB.angle + Math.PI/2);
|
||||
|
||||
// Get distance vector
|
||||
var dist = vec2.create();
|
||||
vec2.sub(dist, bodyB.position, bodyA.position);
|
||||
vec2.scale(tangentA.ri, tangentA.ni, -vec2.dot(tangentA.ni, dist));
|
||||
vec2.scale(tangentB.ri, tangentB.ni, vec2.dot(tangentB.ni, dist));
|
||||
vec2.add(tangentA.rj, tangentA.ri, dist);
|
||||
vec2.sub(tangentB.rj, tangentB.ri, dist);
|
||||
vec2.set(tangentA.ri, 0, 0);
|
||||
vec2.set(tangentB.ri, 0, 0);
|
||||
var eqs = this.equations,
|
||||
trans = eqs[0];
|
||||
trans.update();
|
||||
};
|
||||
|
|
208
src/physics/advanced/constraints/RevoluteConstraint.js
Normal file
|
@ -0,0 +1,208 @@
|
|||
var Constraint = require('./Constraint')
|
||||
, Equation = require('./Equation')
|
||||
, RotationalVelocityEquation = require('./RotationalVelocityEquation')
|
||||
, RotationalLockEquation = require('./RotationalLockEquation')
|
||||
, vec2 = require('../math/vec2')
|
||||
|
||||
module.exports = RevoluteConstraint;
|
||||
|
||||
var worldPivotA = vec2.create(),
|
||||
worldPivotB = vec2.create(),
|
||||
xAxis = vec2.fromValues(1,0),
|
||||
yAxis = vec2.fromValues(0,1),
|
||||
g = vec2.create();
|
||||
|
||||
/**
|
||||
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
|
||||
* @class RevoluteConstraint
|
||||
* @constructor
|
||||
* @author schteppe
|
||||
* @param {Body} bodyA
|
||||
* @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to.
|
||||
* @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point.
|
||||
* @param {Float32Array} pivotB See pivotA.
|
||||
* @param {Number} maxForce The maximum force that should be applied to constrain the bodies.
|
||||
* @extends {Constraint}
|
||||
* @todo Ability to specify world points
|
||||
*/
|
||||
function RevoluteConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){
|
||||
Constraint.call(this,bodyA,bodyB);
|
||||
|
||||
maxForce = typeof(maxForce)!="undefined" ? maxForce : Number.MAX_VALUE;
|
||||
|
||||
this.pivotA = pivotA;
|
||||
this.pivotB = pivotB;
|
||||
|
||||
// Equations to be fed to the solver
|
||||
var eqs = this.equations = [
|
||||
new Equation(bodyA,bodyB,-maxForce,maxForce),
|
||||
new Equation(bodyA,bodyB,-maxForce,maxForce),
|
||||
];
|
||||
|
||||
var x = eqs[0];
|
||||
var y = eqs[1];
|
||||
|
||||
x.computeGq = function(){
|
||||
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
|
||||
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
|
||||
vec2.add(g, bodyB.position, worldPivotB);
|
||||
vec2.sub(g, g, bodyA.position);
|
||||
vec2.sub(g, g, worldPivotA);
|
||||
return vec2.dot(g,xAxis);
|
||||
};
|
||||
|
||||
y.computeGq = function(){
|
||||
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
|
||||
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
|
||||
vec2.add(g, bodyB.position, worldPivotB);
|
||||
vec2.sub(g, g, bodyA.position);
|
||||
vec2.sub(g, g, worldPivotA);
|
||||
return vec2.dot(g,yAxis);
|
||||
};
|
||||
|
||||
y.minForce = x.minForce = -maxForce;
|
||||
y.maxForce = x.maxForce = maxForce;
|
||||
|
||||
this.motorEquation = new RotationalVelocityEquation(bodyA,bodyB);
|
||||
this.motorEnabled = false;
|
||||
|
||||
// Angle limits
|
||||
this.lowerLimitEnabled = false;
|
||||
this.upperLimitEnabled = false;
|
||||
this.lowerLimit = 0;
|
||||
this.upperLimit = 0;
|
||||
this.upperLimitEquation = new RotationalLockEquation(bodyA,bodyB);
|
||||
this.lowerLimitEquation = new RotationalLockEquation(bodyA,bodyB);
|
||||
this.upperLimitEquation.minForce = 0;
|
||||
this.lowerLimitEquation.maxForce = 0;
|
||||
}
|
||||
RevoluteConstraint.prototype = new Constraint();
|
||||
|
||||
RevoluteConstraint.prototype.update = function(){
|
||||
var bodyA = this.bodyA,
|
||||
bodyB = this.bodyB,
|
||||
pivotA = this.pivotA,
|
||||
pivotB = this.pivotB,
|
||||
eqs = this.equations,
|
||||
normal = eqs[0],
|
||||
tangent= eqs[1],
|
||||
x = eqs[0],
|
||||
y = eqs[1],
|
||||
upperLimit = this.upperLimit,
|
||||
lowerLimit = this.lowerLimit,
|
||||
upperLimitEquation = this.upperLimitEquation,
|
||||
lowerLimitEquation = this.lowerLimitEquation;
|
||||
|
||||
var relAngle = this.angle = bodyB.angle - bodyA.angle;
|
||||
|
||||
if(this.upperLimitEnabled && relAngle > upperLimit){
|
||||
upperLimitEquation.angle = upperLimit;
|
||||
if(eqs.indexOf(upperLimitEquation)==-1)
|
||||
eqs.push(upperLimitEquation);
|
||||
} else {
|
||||
var idx = eqs.indexOf(upperLimitEquation);
|
||||
if(idx != -1) eqs.splice(idx,1);
|
||||
}
|
||||
|
||||
if(this.lowerLimitEnabled && relAngle < lowerLimit){
|
||||
lowerLimitEquation.angle = lowerLimit;
|
||||
if(eqs.indexOf(lowerLimitEquation)==-1)
|
||||
eqs.push(lowerLimitEquation);
|
||||
} else {
|
||||
var idx = eqs.indexOf(lowerLimitEquation);
|
||||
if(idx != -1) eqs.splice(idx,1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
The constraint violation is
|
||||
|
||||
g = xj + rj - xi - ri
|
||||
|
||||
...where xi and xj are the body positions and ri and rj world-oriented offset vectors. Differentiate:
|
||||
|
||||
gdot = vj + wj x rj - vi - wi x ri
|
||||
|
||||
We split this into x and y directions. (let x and y be unit vectors along the respective axes)
|
||||
|
||||
gdot * x = ( vj + wj x rj - vi - wi x ri ) * x
|
||||
= ( vj*x + (wj x rj)*x -vi*x -(wi x ri)*x
|
||||
= ( vj*x + (rj x x)*wj -vi*x -(ri x x)*wi
|
||||
= [ -x -(ri x x) x (rj x x)] * [vi wi vj wj]
|
||||
= G*W
|
||||
|
||||
...and similar for y. We have then identified the jacobian entries for x and y directions:
|
||||
|
||||
Gx = [ x (rj x x) -x -(ri x x)]
|
||||
Gy = [ y (rj x y) -y -(ri x y)]
|
||||
|
||||
*/
|
||||
|
||||
vec2.rotate(worldPivotA, pivotA, bodyA.angle);
|
||||
vec2.rotate(worldPivotB, pivotB, bodyB.angle);
|
||||
|
||||
x.G[0] = -1;
|
||||
x.G[1] = 0;
|
||||
x.G[2] = -vec2.crossLength(worldPivotA,xAxis);
|
||||
x.G[3] = 1;
|
||||
x.G[4] = 0;
|
||||
x.G[5] = vec2.crossLength(worldPivotB,xAxis);
|
||||
|
||||
y.G[0] = 0;
|
||||
y.G[1] = -1;
|
||||
y.G[2] = -vec2.crossLength(worldPivotA,yAxis);
|
||||
y.G[3] = 0;
|
||||
y.G[4] = 1;
|
||||
y.G[5] = vec2.crossLength(worldPivotB,yAxis);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable the rotational motor
|
||||
* @method enableMotor
|
||||
*/
|
||||
RevoluteConstraint.prototype.enableMotor = function(){
|
||||
if(this.motorEnabled) return;
|
||||
this.equations.push(this.motorEquation);
|
||||
this.motorEnabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable the rotational motor
|
||||
* @method disableMotor
|
||||
*/
|
||||
RevoluteConstraint.prototype.disableMotor = function(){
|
||||
if(!this.motorEnabled) return;
|
||||
var i = this.equations.indexOf(this.motorEquation);
|
||||
this.equations.splice(i,1);
|
||||
this.motorEnabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the motor is enabled.
|
||||
* @method motorIsEnabled
|
||||
* @return {Boolean}
|
||||
*/
|
||||
RevoluteConstraint.prototype.motorIsEnabled = function(){
|
||||
return !!this.motorEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the speed of the rotational constraint motor
|
||||
* @method setMotorSpeed
|
||||
* @param {Number} speed
|
||||
*/
|
||||
RevoluteConstraint.prototype.setMotorSpeed = function(speed){
|
||||
if(!this.motorEnabled) return;
|
||||
var i = this.equations.indexOf(this.motorEquation);
|
||||
this.equations[i].relativeVelocity = speed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the speed of the rotational constraint motor
|
||||
* @method getMotorSpeed
|
||||
* @return {Number} The current speed, or false if the motor is not enabled.
|
||||
*/
|
||||
RevoluteConstraint.prototype.getMotorSpeed = function(){
|
||||
if(!this.motorEnabled) return false;
|
||||
return this.motorEquation.relativeVelocity;
|
||||
};
|
37
src/physics/advanced/constraints/RotationalLockEquation.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
var Equation = require("./Equation"),
|
||||
vec2 = require('../math/vec2');
|
||||
|
||||
module.exports = RotationalLockEquation;
|
||||
|
||||
/**
|
||||
* Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter.
|
||||
*
|
||||
* @class RotationalLockEquation
|
||||
* @constructor
|
||||
* @extends Equation
|
||||
* @param {Body} bi
|
||||
* @param {Body} bj
|
||||
* @param {Object} options
|
||||
* @param {Number} options.angle Angle to add to the local vector in body i.
|
||||
*/
|
||||
function RotationalLockEquation(bi,bj,options){
|
||||
options = options || {};
|
||||
Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
|
||||
this.angle = options.angle || 0;
|
||||
|
||||
var G = this.G;
|
||||
G[2] = 1;
|
||||
G[5] = -1;
|
||||
};
|
||||
RotationalLockEquation.prototype = new Equation();
|
||||
RotationalLockEquation.prototype.constructor = RotationalLockEquation;
|
||||
|
||||
var worldVectorA = vec2.create(),
|
||||
worldVectorB = vec2.create(),
|
||||
xAxis = vec2.fromValues(1,0),
|
||||
yAxis = vec2.fromValues(0,1);
|
||||
RotationalLockEquation.prototype.computeGq = function(){
|
||||
vec2.rotate(worldVectorA,xAxis,this.bi.angle+this.angle);
|
||||
vec2.rotate(worldVectorB,yAxis,this.bj.angle);
|
||||
return vec2.dot(worldVectorA,worldVectorB);
|
||||
};
|
|
@ -13,58 +13,20 @@ module.exports = RotationalVelocityEquation;
|
|||
* @param {Body} bj
|
||||
*/
|
||||
function RotationalVelocityEquation(bi,bj){
|
||||
Equation.call(this,bi,bj,-1e6,1e6);
|
||||
Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE);
|
||||
this.relativeVelocity = 1;
|
||||
this.ratio = 1;
|
||||
};
|
||||
RotationalVelocityEquation.prototype = new Equation();
|
||||
RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation;
|
||||
RotationalVelocityEquation.prototype.computeB = function(a,b,h){
|
||||
var bi = this.bi,
|
||||
bj = this.bj,
|
||||
vi = bi.velocity,
|
||||
wi = bi.angularVelocity,
|
||||
taui = bi.angularForce,
|
||||
vj = bj.velocity,
|
||||
wj = bj.angularVelocity,
|
||||
tauj = bj.angularForce,
|
||||
invIi = bi.invInertia,
|
||||
invIj = bj.invInertia,
|
||||
Gq = 0,
|
||||
GW = this.ratio * wj - wi + this.relativeVelocity,
|
||||
GiMf = invIj*tauj - invIi*taui;
|
||||
var G = this.G;
|
||||
G[2] = -1;
|
||||
G[5] = this.ratio;
|
||||
|
||||
var B = - Gq * a - GW * b - h*GiMf;
|
||||
var GiMf = this.computeGiMf();
|
||||
var GW = this.computeGW() + this.relativeVelocity;
|
||||
var B = - GW * b - h*GiMf;
|
||||
|
||||
return B;
|
||||
};
|
||||
|
||||
// Compute C = GMG+eps in the SPOOK equation
|
||||
RotationalVelocityEquation.prototype.computeC = function(eps){
|
||||
var bi = this.bi,
|
||||
bj = this.bj;
|
||||
|
||||
var C = bi.invInertia + bj.invInertia + eps;
|
||||
|
||||
return C;
|
||||
};
|
||||
var computeGWlambda_ulambda = vec2.create();
|
||||
RotationalVelocityEquation.prototype.computeGWlambda = function(){
|
||||
var bi = this.bi,
|
||||
bj = this.bj;
|
||||
|
||||
var GWlambda = bj.wlambda - bi.wlambda;
|
||||
|
||||
return GWlambda;
|
||||
};
|
||||
|
||||
var addToWlambda_temp = vec2.create();
|
||||
RotationalVelocityEquation.prototype.addToWlambda = function(deltalambda){
|
||||
var bi = this.bi,
|
||||
bj = this.bj;
|
||||
|
||||
// Add to angular velocity
|
||||
bi.wlambda -= bi.invInertia * deltalambda;
|
||||
bj.wlambda += bj.invInertia * deltalambda;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ EventEmitter.prototype = {
|
|||
constructor: EventEmitter,
|
||||
|
||||
/**
|
||||
* Add an event listener
|
||||
* @method on
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
* Add an event listener
|
||||
* @method on
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
on: function ( type, listener ) {
|
||||
if ( this._listeners === undefined ) this._listeners = {};
|
||||
var listeners = this._listeners;
|
||||
|
@ -30,12 +30,12 @@ EventEmitter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Check if an event listener is added
|
||||
* @method has
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {Boolean}
|
||||
*/
|
||||
* Check if an event listener is added
|
||||
* @method has
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {Boolean}
|
||||
*/
|
||||
has: function ( type, listener ) {
|
||||
if ( this._listeners === undefined ) return false;
|
||||
var listeners = this._listeners;
|
||||
|
@ -46,12 +46,12 @@ EventEmitter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener
|
||||
* @method off
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
* Remove an event listener
|
||||
* @method off
|
||||
* @param {String} type
|
||||
* @param {Function} listener
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
off: function ( type, listener ) {
|
||||
if ( this._listeners === undefined ) return;
|
||||
var listeners = this._listeners;
|
||||
|
@ -63,12 +63,12 @@ EventEmitter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Emit an event.
|
||||
* @method emit
|
||||
* @param {Object} event
|
||||
* @param {String} event.type
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
* Emit an event.
|
||||
* @method emit
|
||||
* @param {Object} event
|
||||
* @param {String} event.type
|
||||
* @return {EventEmitter} The self object, for chainability.
|
||||
*/
|
||||
emit: function ( event ) {
|
||||
if ( this._listeners === undefined ) return;
|
||||
var listeners = this._listeners;
|
||||
|
|
|
@ -17,65 +17,65 @@ function ContactMaterial(materialA, materialB, options){
|
|||
options = options || {};
|
||||
|
||||
/**
|
||||
* The contact material identifier
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
* The contact material identifier
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
this.id = idCounter++;
|
||||
|
||||
/**
|
||||
* First material participating in the contact material
|
||||
* @property materialA
|
||||
* @type {Material}
|
||||
*/
|
||||
* First material participating in the contact material
|
||||
* @property materialA
|
||||
* @type {Material}
|
||||
*/
|
||||
this.materialA = materialA;
|
||||
|
||||
/**
|
||||
* Second material participating in the contact material
|
||||
* @property materialB
|
||||
* @type {Material}
|
||||
*/
|
||||
* Second material participating in the contact material
|
||||
* @property materialB
|
||||
* @type {Material}
|
||||
*/
|
||||
this.materialB = materialB;
|
||||
|
||||
/**
|
||||
* Friction to use in the contact of these two materials
|
||||
* @property friction
|
||||
* @type {Number}
|
||||
*/
|
||||
* Friction to use in the contact of these two materials
|
||||
* @property friction
|
||||
* @type {Number}
|
||||
*/
|
||||
this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3;
|
||||
|
||||
/**
|
||||
* Restitution to use in the contact of these two materials
|
||||
* @property restitution
|
||||
* @type {Number}
|
||||
*/
|
||||
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.3;
|
||||
* Restitution to use in the contact of these two materials
|
||||
* @property restitution
|
||||
* @type {Number}
|
||||
*/
|
||||
this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.0;
|
||||
|
||||
/**
|
||||
* Stiffness of the resulting ContactEquation that this ContactMaterial generate
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
* Stiffness of the resulting ContactEquation that this ContactMaterial generate
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : 1e7;
|
||||
|
||||
/**
|
||||
* Relaxation of the resulting ContactEquation that this ContactMaterial generate
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
* Relaxation of the resulting ContactEquation that this ContactMaterial generate
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3;
|
||||
|
||||
/**
|
||||
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate
|
||||
* @property frictionStiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
* Stiffness of the resulting FrictionEquation that this ContactMaterial generate
|
||||
* @property frictionStiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : 1e7;
|
||||
|
||||
/**
|
||||
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate
|
||||
* @property frictionRelaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
* Relaxation of the resulting FrictionEquation that this ContactMaterial generate
|
||||
* @property frictionRelaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3;
|
||||
};
|
||||
|
|
|
@ -11,9 +11,9 @@ var idCounter = 0;
|
|||
*/
|
||||
function Material(){
|
||||
/**
|
||||
* The material identifier
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
* The material identifier
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
this.id = idCounter++;
|
||||
};
|
||||
|
|
|
@ -194,7 +194,7 @@
|
|||
i++;
|
||||
}
|
||||
}
|
||||
if(iscs.length === 0) return [p.slice(0)];
|
||||
if(iscs.length == 0) return [p.slice(0)];
|
||||
var comp = function(u,v) {return PolyK._P.dist(a,u) - PolyK._P.dist(a,v); }
|
||||
iscs.sort(comp);
|
||||
|
||||
|
@ -226,7 +226,7 @@
|
|||
ps = PolyK._getPoints(ps, ind1, ind0);
|
||||
i0.flag = i1.flag = false;
|
||||
iscs.splice(0,2);
|
||||
if(iscs.length === 0) pgs.push(ps);
|
||||
if(iscs.length == 0) pgs.push(ps);
|
||||
}
|
||||
else { dir++; iscs.reverse(); }
|
||||
if(dir>1) break;
|
||||
|
@ -402,7 +402,7 @@
|
|||
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
|
||||
|
||||
var Den = dax*dby - day*dbx;
|
||||
if (Den === 0) return null; // parallel
|
||||
if (Den == 0) return null; // parallel
|
||||
|
||||
var A = (a1.x * a2.y - a1.y * a2.x);
|
||||
var B = (b1.x * b2.y - b1.y * b2.x);
|
||||
|
@ -424,7 +424,7 @@
|
|||
var day = (a1.y-a2.y), dby = (b1.y-b2.y);
|
||||
|
||||
var Den = dax*dby - day*dbx;
|
||||
if (Den === 0) return null; // parallel
|
||||
if (Den == 0) return null; // parallel
|
||||
|
||||
var A = (a1.x * a2.y - a1.y * a2.x);
|
||||
var B = (b1.x * b2.y - b1.y * b2.x);
|
||||
|
@ -472,6 +472,6 @@
|
|||
|
||||
PolyK._tp = [];
|
||||
for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0));
|
||||
*/
|
||||
*/
|
||||
|
||||
module.exports = PolyK;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
var vec2 = require('../math/vec2');
|
||||
var vec2 = require('../math/vec2')
|
||||
, decomp = require('poly-decomp')
|
||||
, Convex = require('../shapes/Convex')
|
||||
|
||||
module.exports = Body;
|
||||
|
||||
|
@ -25,156 +27,176 @@ function Body(options){
|
|||
options = options || {};
|
||||
|
||||
/**
|
||||
* The body identifyer
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
* The body identifyer
|
||||
* @property id
|
||||
* @type {Number}
|
||||
*/
|
||||
this.id = ++Body._idCounter;
|
||||
|
||||
/**
|
||||
* The shapes of the body. The local transform of the shape in .shapes[i] is
|
||||
* defined by .shapeOffsets[i] and .shapeAngles[i].
|
||||
*
|
||||
* @property shapes
|
||||
* @type {Array}
|
||||
*/
|
||||
* The shapes of the body. The local transform of the shape in .shapes[i] is
|
||||
* defined by .shapeOffsets[i] and .shapeAngles[i].
|
||||
*
|
||||
* @property shapes
|
||||
* @type {Array}
|
||||
*/
|
||||
this.shapes = [];
|
||||
|
||||
/**
|
||||
* The local shape offsets, relative to the body center of mass. This is an
|
||||
* array of Float32Array.
|
||||
* @property shapeOffsets
|
||||
* @type {Array}
|
||||
*/
|
||||
* The local shape offsets, relative to the body center of mass. This is an
|
||||
* array of Float32Array.
|
||||
* @property shapeOffsets
|
||||
* @type {Array}
|
||||
*/
|
||||
this.shapeOffsets = [];
|
||||
|
||||
/**
|
||||
* The body-local shape angle transforms. This is an array of numbers (angles).
|
||||
* @property shapeAngles
|
||||
* @type {Array}
|
||||
*/
|
||||
* The body-local shape angle transforms. This is an array of numbers (angles).
|
||||
* @property shapeAngles
|
||||
* @type {Array}
|
||||
*/
|
||||
this.shapeAngles = [];
|
||||
|
||||
/**
|
||||
* The mass of the body.
|
||||
* @property mass
|
||||
* @type {number}
|
||||
*/
|
||||
* The mass of the body.
|
||||
* @property mass
|
||||
* @type {number}
|
||||
*/
|
||||
this.mass = options.mass || 0;
|
||||
|
||||
/**
|
||||
* The inverse mass of the body.
|
||||
* @property invMass
|
||||
* @type {number}
|
||||
*/
|
||||
* The inverse mass of the body.
|
||||
* @property invMass
|
||||
* @type {number}
|
||||
*/
|
||||
this.invMass = 0;
|
||||
|
||||
/**
|
||||
* The inertia of the body around the Z axis.
|
||||
* @property inertia
|
||||
* @type {number}
|
||||
*/
|
||||
* The inertia of the body around the Z axis.
|
||||
* @property inertia
|
||||
* @type {number}
|
||||
*/
|
||||
this.inertia = 0;
|
||||
|
||||
/**
|
||||
* The inverse inertia of the body.
|
||||
* @property invInertia
|
||||
* @type {number}
|
||||
*/
|
||||
* The inverse inertia of the body.
|
||||
* @property invInertia
|
||||
* @type {number}
|
||||
*/
|
||||
this.invInertia = 0;
|
||||
|
||||
this.updateMassProperties();
|
||||
|
||||
/**
|
||||
* The position of the body
|
||||
* @property position
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* The position of the body
|
||||
* @property position
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.position = vec2.fromValues(0,0);
|
||||
if(options.position) vec2.copy(this.position, options.position);
|
||||
|
||||
/**
|
||||
* The velocity of the body
|
||||
* @property velocity
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* The velocity of the body
|
||||
* @property velocity
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.velocity = vec2.fromValues(0,0);
|
||||
if(options.velocity) vec2.copy(this.velocity, options.velocity);
|
||||
|
||||
/**
|
||||
* Constraint velocity that was added to the body during the last step.
|
||||
* @property vlambda
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* Constraint velocity that was added to the body during the last step.
|
||||
* @property vlambda
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.vlambda = vec2.fromValues(0,0);
|
||||
|
||||
/**
|
||||
* Angular constraint velocity that was added to the body during last step.
|
||||
* @property wlambda
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* Angular constraint velocity that was added to the body during last step.
|
||||
* @property wlambda
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.wlambda = 0;
|
||||
|
||||
/**
|
||||
* The angle of the body
|
||||
* @property angle
|
||||
* @type {number}
|
||||
*/
|
||||
* The angle of the body
|
||||
* @property angle
|
||||
* @type {number}
|
||||
*/
|
||||
this.angle = options.angle || 0;
|
||||
|
||||
/**
|
||||
* The angular velocity of the body
|
||||
* @property angularVelocity
|
||||
* @type {number}
|
||||
*/
|
||||
* The angular velocity of the body
|
||||
* @property angularVelocity
|
||||
* @type {number}
|
||||
*/
|
||||
this.angularVelocity = options.angularVelocity || 0;
|
||||
|
||||
/**
|
||||
* The force acting on the body
|
||||
* @property force
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* The force acting on the body
|
||||
* @property force
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.force = vec2.create();
|
||||
if(options.force) vec2.copy(this.force, options.force);
|
||||
|
||||
/**
|
||||
* The angular force acting on the body
|
||||
* @property angularForce
|
||||
* @type {number}
|
||||
*/
|
||||
* The angular force acting on the body
|
||||
* @property angularForce
|
||||
* @type {number}
|
||||
*/
|
||||
this.angularForce = options.angularForce || 0;
|
||||
|
||||
/**
|
||||
* The type of motion this body has. Should be one of: Body.STATIC (the body
|
||||
* does not move), Body.DYNAMIC (body can move and respond to collisions)
|
||||
* and Body.KINEMATIC (only moves according to its .velocity).
|
||||
*
|
||||
* @property motionState
|
||||
* @type {number}
|
||||
*
|
||||
* @example
|
||||
* // This body will move and interact with other bodies
|
||||
* var dynamicBody = new Body();
|
||||
* dynamicBody.motionState = Body.DYNAMIC;
|
||||
*
|
||||
* @example
|
||||
* // This body will not move at all
|
||||
* var staticBody = new Body();
|
||||
* staticBody.motionState = Body.STATIC;
|
||||
*
|
||||
* @example
|
||||
* // This body will only move if you change its velocity
|
||||
* var kinematicBody = new Body();
|
||||
* kinematicBody.motionState = Body.KINEMATIC;
|
||||
*/
|
||||
this.motionState = this.mass === 0 ? Body.STATIC : Body.DYNAMIC;
|
||||
* The linear damping acting on the body in the velocity direction
|
||||
* @property damping
|
||||
* @type {Number}
|
||||
*/
|
||||
this.damping = typeof(options.damping)=="number" ? options.damping : 0.1;
|
||||
|
||||
/**
|
||||
* Bounding circle radius
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
* The angular force acting on the body
|
||||
* @property angularDamping
|
||||
* @type {Number}
|
||||
*/
|
||||
this.angularDamping = typeof(options.angularDamping)=="number" ? options.angularDamping : 0.1;
|
||||
|
||||
/**
|
||||
* The type of motion this body has. Should be one of: Body.STATIC (the body
|
||||
* does not move), Body.DYNAMIC (body can move and respond to collisions)
|
||||
* and Body.KINEMATIC (only moves according to its .velocity).
|
||||
*
|
||||
* @property motionState
|
||||
* @type {number}
|
||||
*
|
||||
* @example
|
||||
* // This body will move and interact with other bodies
|
||||
* var dynamicBody = new Body();
|
||||
* dynamicBody.motionState = Body.DYNAMIC;
|
||||
*
|
||||
* @example
|
||||
* // This body will not move at all
|
||||
* var staticBody = new Body();
|
||||
* staticBody.motionState = Body.STATIC;
|
||||
*
|
||||
* @example
|
||||
* // This body will only move if you change its velocity
|
||||
* var kinematicBody = new Body();
|
||||
* kinematicBody.motionState = Body.KINEMATIC;
|
||||
*/
|
||||
this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC;
|
||||
|
||||
/**
|
||||
* Bounding circle radius
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
this.boundingRadius = 0;
|
||||
|
||||
this.concavePath = null;
|
||||
|
||||
this.lastDampingScale = 1;
|
||||
this.lastAngularDampingScale = 1;
|
||||
this.lastDampingTimeStep = -1;
|
||||
};
|
||||
|
||||
Body._idCounter = 0;
|
||||
|
@ -225,6 +247,15 @@ Body.prototype.updateBoundingRadius = function(){
|
|||
* body.addShape(shape,[0,1],Math.PI/2);
|
||||
*/
|
||||
Body.prototype.addShape = function(shape,offset,angle){
|
||||
angle = angle || 0.0;
|
||||
|
||||
// Copy the offset vector
|
||||
if(offset){
|
||||
offset = vec2.fromValues(offset[0],offset[1]);
|
||||
} else {
|
||||
offset = vec2.fromValues(0,0);
|
||||
}
|
||||
|
||||
this.shapes .push(shape);
|
||||
this.shapeOffsets.push(offset);
|
||||
this.shapeAngles .push(angle);
|
||||
|
@ -232,6 +263,24 @@ Body.prototype.addShape = function(shape,offset,angle){
|
|||
this.updateBoundingRadius();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a shape
|
||||
* @method removeShape
|
||||
* @param {Shape} shape
|
||||
* @return {Boolean} True if the shape was found and removed, else false.
|
||||
*/
|
||||
Body.prototype.removeShape = function(shape){
|
||||
var idx = this.shapes.indexOf(shape);
|
||||
|
||||
if(idx != -1){
|
||||
this.shapes.splice(idx,1);
|
||||
this.shapeOffsets.splice(idx,1);
|
||||
this.shapeAngles.splice(idx,1);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates .inertia, .invMass, .invInertia for this Body. Should be called when
|
||||
* changing the structure or mass of the Body.
|
||||
|
@ -305,6 +354,175 @@ Body.prototype.toWorldFrame = function(out, localPoint){
|
|||
vec2.toGlobalFrame(out, localPoint, this.position, this.angle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points.
|
||||
* @method fromPolygon
|
||||
* @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
|
||||
* @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself.
|
||||
* @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
|
||||
* @return {Boolean} True on success, else false.
|
||||
*/
|
||||
Body.prototype.fromPolygon = function(path,options){
|
||||
options = options || {};
|
||||
|
||||
// Remove all shapes
|
||||
for(var i=this.shapes.length; i>=0; --i)
|
||||
this.removeShape(this.shapes[i]);
|
||||
|
||||
var p = new decomp.Polygon();
|
||||
p.vertices = path;
|
||||
|
||||
// Make it counter-clockwise
|
||||
p.makeCCW();
|
||||
|
||||
if(typeof(options.removeCollinearPoints)=="number"){
|
||||
p.removeCollinearPoints(options.removeCollinearPoints);
|
||||
}
|
||||
|
||||
// Check if any line segment intersects the path itself
|
||||
if(typeof(options.skipSimpleCheck) == "undefined"){
|
||||
if(!p.isSimple()) return false;
|
||||
}
|
||||
|
||||
// Save this path for later
|
||||
this.concavePath = p.vertices.slice(0);
|
||||
for(var i=0; i<this.concavePath.length; i++){
|
||||
var v = [0,0];
|
||||
vec2.copy(v,this.concavePath[i]);
|
||||
this.concavePath[i] = v;
|
||||
}
|
||||
|
||||
// Slow or fast decomp?
|
||||
var convexes;
|
||||
if(options.optimalDecomp) convexes = p.decomp();
|
||||
else convexes = p.quickDecomp();
|
||||
|
||||
var cm = vec2.create();
|
||||
|
||||
// Add convexes
|
||||
for(var i=0; i!==convexes.length; i++){
|
||||
// Create convex
|
||||
var c = new Convex(convexes[i].vertices);
|
||||
|
||||
// Move all vertices so its center of mass is in the local center of the convex
|
||||
for(var j=0; j!==c.vertices.length; j++){
|
||||
var v = c.vertices[j];
|
||||
vec2.sub(v,v,c.centerOfMass);
|
||||
}
|
||||
|
||||
vec2.scale(cm,c.centerOfMass,1);
|
||||
c.updateTriangles();
|
||||
c.updateCenterOfMass();
|
||||
c.updateBoundingRadius();
|
||||
|
||||
// Add the shape
|
||||
this.addShape(c,cm);
|
||||
}
|
||||
|
||||
this.adjustCenterOfMass();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
var adjustCenterOfMass_tmp1 = vec2.fromValues(0,0),
|
||||
adjustCenterOfMass_tmp2 = vec2.fromValues(0,0),
|
||||
adjustCenterOfMass_tmp3 = vec2.fromValues(0,0),
|
||||
adjustCenterOfMass_tmp4 = vec2.fromValues(0,0);
|
||||
|
||||
/**
|
||||
* Moves the shape offsets so their center of mass becomes the body center of mass.
|
||||
* @method adjustCenterOfMass
|
||||
*/
|
||||
Body.prototype.adjustCenterOfMass = function(){
|
||||
var zero = adjustCenterOfMass_tmp1,
|
||||
offset_times_area = adjustCenterOfMass_tmp2,
|
||||
sum = adjustCenterOfMass_tmp3,
|
||||
cm = adjustCenterOfMass_tmp4,
|
||||
totalArea = 0;
|
||||
vec2.set(sum,0,0);
|
||||
vec2.set(zero,0,0);
|
||||
|
||||
for(var i=0; i!==this.shapes.length; i++){
|
||||
var s = this.shapes[i],
|
||||
offset = this.shapeOffsets[i] || zero;
|
||||
vec2.scale(offset_times_area,offset,s.area);
|
||||
vec2.add(sum,sum,offset_times_area);
|
||||
totalArea += s.area;
|
||||
}
|
||||
|
||||
vec2.scale(cm,sum,1/totalArea);
|
||||
|
||||
// Now move all shapes
|
||||
for(var i=0; i!==this.shapes.length; i++){
|
||||
var s = this.shapes[i],
|
||||
offset = this.shapeOffsets[i];
|
||||
|
||||
// Offset may be undefined. Fix that.
|
||||
if(!offset){
|
||||
offset = this.shapeOffsets[i] = vec2.create();
|
||||
}
|
||||
|
||||
vec2.sub(offset,offset,cm);
|
||||
}
|
||||
|
||||
// Move the body position too
|
||||
vec2.add(this.position,this.position,cm);
|
||||
|
||||
// And concave path
|
||||
for(var i=0; this.concavePath && i<this.concavePath.length; i++){
|
||||
vec2.sub(this.concavePath[i], this.concavePath[i], cm);
|
||||
}
|
||||
|
||||
this.updateMassProperties();
|
||||
this.updateBoundingRadius();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the force on the body to zero.
|
||||
* @method setZeroForce
|
||||
*/
|
||||
Body.prototype.setZeroForce = function(){
|
||||
vec2.set(this.force,0.0,0.0);
|
||||
this.angularForce = 0.0;
|
||||
};
|
||||
|
||||
Body.prototype.resetConstraintVelocity = function(){
|
||||
var b = this,
|
||||
vlambda = b.vlambda;
|
||||
vec2.set(vlambda,0,0);
|
||||
b.wlambda = 0;
|
||||
};
|
||||
|
||||
Body.prototype.addConstraintVelocity = function(){
|
||||
var b = this,
|
||||
v = b.velocity;
|
||||
vec2.add( v, v, b.vlambda);
|
||||
b.angularVelocity += b.wlambda;
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply damping, see <a href="http://code.google.com/p/bullet/issues/detail?id=74">this</a> for details.
|
||||
* @method applyDamping
|
||||
* @param {number} dt Current time step
|
||||
*/
|
||||
Body.prototype.applyDamping = function(dt){
|
||||
if(this.motionState & Body.DYNAMIC){ // Only for dynamic bodies
|
||||
|
||||
// Since Math.pow generates garbage we check if we can reuse the scaling coefficient from last step
|
||||
if(dt != this.lastDampingTimeStep){
|
||||
this.lastDampingScale = Math.pow(1.0 - this.damping,dt);
|
||||
this.lastAngularDampingScale = Math.pow(1.0 - this.angularDamping,dt);
|
||||
this.lastDampingTimeStep = dt;
|
||||
}
|
||||
|
||||
var v = this.velocity;
|
||||
vec2.scale(v,v,this.lastDampingScale);
|
||||
this.angularVelocity *= this.lastAngularDampingScale;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic body.
|
||||
* @property DYNAMIC
|
||||
|
|
|
@ -22,52 +22,52 @@ function Spring(bodyA,bodyB,options){
|
|||
options = options || {};
|
||||
|
||||
/**
|
||||
* Rest length of the spring.
|
||||
* @property restLength
|
||||
* @type {number}
|
||||
*/
|
||||
* Rest length of the spring.
|
||||
* @property restLength
|
||||
* @type {number}
|
||||
*/
|
||||
this.restLength = typeof(options.restLength)=="number" ? options.restLength : 1;
|
||||
|
||||
/**
|
||||
* Stiffness of the spring.
|
||||
* @property stiffness
|
||||
* @type {number}
|
||||
*/
|
||||
* Stiffness of the spring.
|
||||
* @property stiffness
|
||||
* @type {number}
|
||||
*/
|
||||
this.stiffness = options.stiffness || 100;
|
||||
|
||||
/**
|
||||
* Damping of the spring.
|
||||
* @property damping
|
||||
* @type {number}
|
||||
*/
|
||||
* Damping of the spring.
|
||||
* @property damping
|
||||
* @type {number}
|
||||
*/
|
||||
this.damping = options.damping || 1;
|
||||
|
||||
/**
|
||||
* First connected body.
|
||||
* @property bodyA
|
||||
* @type {Body}
|
||||
*/
|
||||
* First connected body.
|
||||
* @property bodyA
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bodyA = bodyA;
|
||||
|
||||
/**
|
||||
* Second connected body.
|
||||
* @property bodyB
|
||||
* @type {Body}
|
||||
*/
|
||||
* Second connected body.
|
||||
* @property bodyB
|
||||
* @type {Body}
|
||||
*/
|
||||
this.bodyB = bodyB;
|
||||
|
||||
/**
|
||||
* Anchor for bodyA in local bodyA coordinates.
|
||||
* @property localAnchorA
|
||||
* @type {Array}
|
||||
*/
|
||||
* Anchor for bodyA in local bodyA coordinates.
|
||||
* @property localAnchorA
|
||||
* @type {Array}
|
||||
*/
|
||||
this.localAnchorA = vec2.fromValues(0,0);
|
||||
|
||||
/**
|
||||
* Anchor for bodyB in local bodyB coordinates.
|
||||
* @property localAnchorB
|
||||
* @type {Array}
|
||||
*/
|
||||
* Anchor for bodyB in local bodyB coordinates.
|
||||
* @property localAnchorB
|
||||
* @type {Array}
|
||||
*/
|
||||
this.localAnchorB = vec2.fromValues(0,0);
|
||||
|
||||
if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA);
|
||||
|
|
|
@ -17,11 +17,12 @@ module.exports = {
|
|||
Island : require('./solver/IslandSolver'),
|
||||
IslandSolver : require('./solver/IslandSolver'),
|
||||
Line : require('./shapes/Line'),
|
||||
LockConstraint : require('./constraints/LockConstraint'),
|
||||
Material : require('./material/Material'),
|
||||
NaiveBroadphase : require('./collision/NaiveBroadphase'),
|
||||
Particle : require('./shapes/Particle'),
|
||||
Plane : require('./shapes/Plane'),
|
||||
PointToPointConstraint : require('./constraints/PointToPointConstraint'),
|
||||
RevoluteConstraint : require('./constraints/RevoluteConstraint'),
|
||||
PrismaticConstraint : require('./constraints/PrismaticConstraint'),
|
||||
Rectangle : require('./shapes/Rectangle'),
|
||||
RotationalVelocityEquation : require('./constraints/RotationalVelocityEquation'),
|
||||
|
|
146
src/physics/advanced/serializer/Serializer.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
var World = require('../world/World')
|
||||
|
||||
var num = { type:"number", required:true };
|
||||
|
||||
/*
|
||||
* Serialize a World instance to JSON
|
||||
* @method serialize
|
||||
* @param {World} world
|
||||
* @return {Object}
|
||||
*/
|
||||
exports.serialize = function(world){
|
||||
return {};
|
||||
};
|
||||
|
||||
/*
|
||||
* Load a World instance from JSON
|
||||
* @param {Object} json
|
||||
* @return {World}
|
||||
*/
|
||||
exports.deserialize = function(json){
|
||||
var world = new World();
|
||||
return world;
|
||||
};
|
||||
|
||||
var schemas = exports.schemas = {};
|
||||
|
||||
schemas['0.3.0'] = {
|
||||
type: "object",
|
||||
additionalProperties:false,
|
||||
properties: {
|
||||
gravity: { $ref:"vec2" },
|
||||
p2: { type:"string", pattern:"^0.3$", required:true },
|
||||
solver: { type:"object", required:true },
|
||||
broadphase: { type:"object", required:true },
|
||||
bodies: {
|
||||
type:"array",
|
||||
required:true,
|
||||
additionalItems:false,
|
||||
items:{
|
||||
type:"object",
|
||||
additionalProperties:false,
|
||||
properties:{
|
||||
id : num,
|
||||
mass : num,
|
||||
angle : num,
|
||||
position : { $ref:"vec2" },
|
||||
velocity : { $ref:"vec2" },
|
||||
angularVelocity : num,
|
||||
force : { $ref:"vec2" },
|
||||
shapes : { required:true, type:"array" },
|
||||
concavePath : { required:true, type:["array","null"] },
|
||||
},
|
||||
}
|
||||
},
|
||||
springs: {
|
||||
type:"array",
|
||||
required:true,
|
||||
additionalItems:false,
|
||||
items:{
|
||||
type:"object",
|
||||
additionalProperties:false,
|
||||
properties:{
|
||||
bodyA : num,
|
||||
bodyB : num,
|
||||
stiffness : num,
|
||||
damping : num,
|
||||
restLength : num,
|
||||
localAnchorA : { $ref:"vec2" },
|
||||
localAnchorB : { $ref:"vec2" },
|
||||
},
|
||||
},
|
||||
},
|
||||
constraints: {
|
||||
type:"array",
|
||||
required:true,
|
||||
items:[{
|
||||
type:"object",
|
||||
additionalProperties:false,
|
||||
properties:{
|
||||
bodyA: num,
|
||||
bodyB: num,
|
||||
type: { type:"string", match:"^DistanceConstraint$" },
|
||||
distance: num,
|
||||
maxForce: num,
|
||||
},
|
||||
},{
|
||||
type:"object",
|
||||
additionalProperties:false,
|
||||
properties:{
|
||||
bodyA: num,
|
||||
bodyB: num,
|
||||
type: { type:"string", match:"^PrismaticConstraint$" },
|
||||
localAxisA: { $ref:"vec2" },
|
||||
localAxisB: { $ref:"vec2" },
|
||||
maxForce: num,
|
||||
},
|
||||
},{
|
||||
type:"object",
|
||||
additionalProperties:false,
|
||||
properties:{
|
||||
bodyA: num,
|
||||
bodyB: num,
|
||||
type: { type:"string", match:'^RevoluteConstraint$' },
|
||||
pivotA: { $ref:"vec2" },
|
||||
pivotB: { $ref:"vec2" },
|
||||
maxForce: num,
|
||||
motorSpeed: { type:["number","boolean"] },
|
||||
lowerLimit: num,
|
||||
lowerLimitEnabled: { type:"boolean" },
|
||||
upperLimit: num,
|
||||
upperLimitEnabled: { type:"boolean" },
|
||||
},
|
||||
}],
|
||||
},
|
||||
contactMaterials: {
|
||||
type:"array",
|
||||
required:true,
|
||||
additionalItems:false,
|
||||
items: {
|
||||
properties : {
|
||||
id: num,
|
||||
materialA: num,
|
||||
materialB: num,
|
||||
friction: num,
|
||||
restitution: num,
|
||||
stiffness: num,
|
||||
relaxation: num,
|
||||
frictionStiffness: num,
|
||||
frictionRelaxation: num,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
exports.vec2 = {
|
||||
id: "/vec2",
|
||||
type:"array",
|
||||
maxItems:2,
|
||||
minItems:2,
|
||||
items:{
|
||||
type:"number",
|
||||
},
|
||||
additionalItems:false,
|
||||
required:true,
|
||||
};
|
|
@ -7,15 +7,15 @@ module.exports = Circle;
|
|||
* @class Circle
|
||||
* @extends {Shape}
|
||||
* @constructor
|
||||
* @param {number} radius
|
||||
* @param {number} radius The radius of this circle
|
||||
*/
|
||||
function Circle(radius){
|
||||
|
||||
/**
|
||||
* The radius of the circle.
|
||||
* @property radius
|
||||
* @type {number}
|
||||
*/
|
||||
* The radius of the circle.
|
||||
* @property radius
|
||||
* @type {number}
|
||||
*/
|
||||
this.radius = radius || 1;
|
||||
|
||||
Shape.call(this,Shape.CIRCLE);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var Shape = require('./Shape')
|
||||
, vec2 = require('../math/vec2')
|
||||
, polyk = require('../math/polyk')
|
||||
, decomp = require('poly-decomp')
|
||||
|
||||
module.exports = Convex;
|
||||
|
||||
|
@ -14,24 +15,31 @@ module.exports = Convex;
|
|||
function Convex(vertices){
|
||||
|
||||
/**
|
||||
* Vertices defined in the local frame.
|
||||
* @property vertices
|
||||
* @type {Array}
|
||||
*/
|
||||
* Vertices defined in the local frame.
|
||||
* @property vertices
|
||||
* @type {Array}
|
||||
*/
|
||||
this.vertices = vertices || [];
|
||||
|
||||
// Copy the verts
|
||||
for(var i=0; i<this.vertices.length; i++){
|
||||
var v = vec2.fromValues();
|
||||
vec2.copy(v,this.vertices[i]);
|
||||
this.vertices[i] = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* The center of mass of the Convex
|
||||
* @property centerOfMass
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
* The center of mass of the Convex
|
||||
* @property centerOfMass
|
||||
* @type {Float32Array}
|
||||
*/
|
||||
this.centerOfMass = vec2.fromValues(0,0);
|
||||
|
||||
/**
|
||||
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
|
||||
* @property triangles
|
||||
* @type {Array}
|
||||
*/
|
||||
* Triangulated version of this convex. The structure is Array of 3-Arrays, and each subarray contains 3 integers, referencing the vertices.
|
||||
* @property triangles
|
||||
* @type {Array}
|
||||
*/
|
||||
this.triangles = [];
|
||||
|
||||
if(this.vertices.length){
|
||||
|
@ -40,10 +48,10 @@ function Convex(vertices){
|
|||
}
|
||||
|
||||
/**
|
||||
* The bounding radius of the convex
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
* The bounding radius of the convex
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
this.boundingRadius = 0;
|
||||
this.updateBoundingRadius();
|
||||
|
||||
|
@ -51,6 +59,10 @@ function Convex(vertices){
|
|||
};
|
||||
Convex.prototype = new Shape();
|
||||
|
||||
/**
|
||||
* Update the .triangles property
|
||||
* @method updateTriangles
|
||||
*/
|
||||
Convex.prototype.updateTriangles = function(){
|
||||
|
||||
this.triangles.length = 0;
|
||||
|
@ -85,6 +97,11 @@ var updateCenterOfMass_centroid = vec2.create(),
|
|||
updateCenterOfMass_ca = vec2.create(),
|
||||
updateCenterOfMass_cb = vec2.create(),
|
||||
updateCenterOfMass_n = vec2.create();
|
||||
|
||||
/**
|
||||
* Update the .centerOfMass property.
|
||||
* @method updateCenterOfMass
|
||||
*/
|
||||
Convex.prototype.updateCenterOfMass = function(){
|
||||
var triangles = this.triangles,
|
||||
verts = this.vertices,
|
||||
|
@ -100,8 +117,9 @@ Convex.prototype.updateCenterOfMass = function(){
|
|||
centroid_times_mass = updateCenterOfMass_centroid_times_mass;
|
||||
|
||||
vec2.set(cm,0,0);
|
||||
var totalArea = 0;
|
||||
|
||||
for(var i=0; i<triangles.length; i++){
|
||||
for(var i=0; i!==triangles.length; i++){
|
||||
var t = triangles[i],
|
||||
a = verts[t[0]],
|
||||
b = verts[t[1]],
|
||||
|
@ -109,17 +127,17 @@ Convex.prototype.updateCenterOfMass = function(){
|
|||
|
||||
vec2.centroid(centroid,a,b,c);
|
||||
|
||||
vec2.sub(ca, c, a);
|
||||
vec2.sub(cb, c, b);
|
||||
|
||||
// Get mass for the triangle (density=1 in this case)
|
||||
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
|
||||
var m = 0.5 * vec2.crossLength(ca,cb);
|
||||
var m = decomp.Point.area(a,b,c)
|
||||
totalArea += m;
|
||||
|
||||
// Add to center of mass
|
||||
vec2.scale(centroid_times_mass, centroid, m);
|
||||
vec2.add(cm, cm, centroid_times_mass);
|
||||
}
|
||||
|
||||
vec2.scale(cm,cm,1/totalArea);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -148,7 +166,8 @@ Convex.prototype.computeMomentOfInertia = function(mass){
|
|||
|
||||
// Get total convex area and density
|
||||
var area = polyk.GetArea(polykVerts);
|
||||
var density = mass / area;
|
||||
this.updateArea();
|
||||
var density = mass / this.area;
|
||||
|
||||
// Temp vectors
|
||||
var a = vec2.create(),
|
||||
|
@ -177,15 +196,15 @@ Convex.prototype.computeMomentOfInertia = function(mass){
|
|||
vec2.sub(ca, c, a);
|
||||
vec2.sub(cb, c, b);
|
||||
|
||||
var area_triangle = 0.5 * vec2.crossLength(ca,cb);
|
||||
var area_triangle = decomp.Point.area(a,b,c)
|
||||
var base = vec2.length(ca);
|
||||
var height = 2*area_triangle / base; // a=b*h/2 => h=2*a/b
|
||||
|
||||
// Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
|
||||
var I_triangle = (base * (Math.pow(height,3))) / 36;
|
||||
|
||||
// Get mass for the triangle
|
||||
var m = base*height/2 * density;
|
||||
var m = area_triangle * density;
|
||||
|
||||
// Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m
|
||||
var I_triangle = m*(base * (Math.pow(height,3))) / 36;
|
||||
|
||||
// Add to total inertia using parallel axis theorem
|
||||
var r2 = vec2.squaredLength(centroid);
|
||||
|
@ -211,3 +230,26 @@ Convex.prototype.updateBoundingRadius = function(){
|
|||
this.boundingRadius = Math.sqrt(r2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the .area
|
||||
* @method updateArea
|
||||
*/
|
||||
Convex.prototype.updateArea = function(){
|
||||
this.updateTriangles();
|
||||
this.area = 0;
|
||||
|
||||
var triangles = this.triangles,
|
||||
verts = this.vertices;
|
||||
for(var i=0; i!==triangles.length; i++){
|
||||
var t = triangles[i],
|
||||
a = verts[t[0]],
|
||||
b = verts[t[1]],
|
||||
c = verts[t[2]];
|
||||
|
||||
// Get mass for the triangle (density=1 in this case)
|
||||
// http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors
|
||||
var m = decomp.Point.area(a,b,c)
|
||||
this.area += m;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,16 +5,17 @@ module.exports = Line;
|
|||
/**
|
||||
* Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0].
|
||||
* @class Line
|
||||
* @param {Number} length The total length of the line
|
||||
* @extends {Shape}
|
||||
* @constructor
|
||||
*/
|
||||
function Line(length){
|
||||
|
||||
/**
|
||||
* Length of this line
|
||||
* @property length
|
||||
* @type {Number}
|
||||
*/
|
||||
* Length of this line
|
||||
* @property length
|
||||
* @type {Number}
|
||||
*/
|
||||
this.length = length;
|
||||
|
||||
Shape.call(this,Shape.LINE);
|
||||
|
|
|
@ -12,10 +12,19 @@ function Plane(){
|
|||
Shape.call(this,Shape.PLANE);
|
||||
};
|
||||
Plane.prototype = new Shape();
|
||||
|
||||
/**
|
||||
* Compute moment of inertia
|
||||
* @method computeMomentOfInertia
|
||||
*/
|
||||
Plane.prototype.computeMomentOfInertia = function(mass){
|
||||
return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the bounding radius
|
||||
* @method updateBoundingRadius
|
||||
*/
|
||||
Plane.prototype.updateBoundingRadius = function(){
|
||||
this.boundingRadius = Number.MAX_VALUE;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,8 @@ module.exports = Rectangle;
|
|||
* Rectangle shape class.
|
||||
* @class Rectangle
|
||||
* @constructor
|
||||
* @param {Number} w Width
|
||||
* @param {Number} h Height
|
||||
* @extends {Convex}
|
||||
*/
|
||||
function Rectangle(w,h){
|
||||
|
@ -16,7 +18,18 @@ function Rectangle(w,h){
|
|||
vec2.fromValues( w/2, h/2),
|
||||
vec2.fromValues(-w/2, h/2)];
|
||||
|
||||
/**
|
||||
* Total width of the rectangle
|
||||
* @property width
|
||||
* @type {Number}
|
||||
*/
|
||||
this.width = w;
|
||||
|
||||
/**
|
||||
* Total height of the rectangle
|
||||
* @property height
|
||||
* @type {Number}
|
||||
*/
|
||||
this.height = h;
|
||||
|
||||
Convex.call(this,verts);
|
||||
|
@ -35,6 +48,10 @@ Rectangle.prototype.computeMomentOfInertia = function(mass){
|
|||
return mass * (h*h + w*w) / 12;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the bounding radius
|
||||
* @method updateBoundingRadius
|
||||
*/
|
||||
Rectangle.prototype.updateBoundingRadius = function(){
|
||||
var w = this.width,
|
||||
h = this.height;
|
||||
|
|
|
@ -9,57 +9,66 @@ function Shape(type){
|
|||
this.type = type;
|
||||
|
||||
/**
|
||||
* Bounding circle radius of this shape
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
* Bounding circle radius of this shape
|
||||
* @property boundingRadius
|
||||
* @type {Number}
|
||||
*/
|
||||
this.boundingRadius = 0;
|
||||
|
||||
/**
|
||||
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>.
|
||||
* @property collisionGroup
|
||||
* @type {Number}
|
||||
* @example
|
||||
* // Setup bits for each available group
|
||||
* var PLAYER = Math.pow(2,0),
|
||||
* ENEMY = Math.pow(2,1),
|
||||
* GROUND = Math.pow(2,2)
|
||||
*
|
||||
* // Put shapes into their groups
|
||||
* player1Shape.collisionGroup = PLAYER;
|
||||
* player2Shape.collisionGroup = PLAYER;
|
||||
* enemyShape .collisionGroup = ENEMY;
|
||||
* groundShape .collisionGroup = GROUND;
|
||||
*
|
||||
* // Assign groups that each shape collide with.
|
||||
* // Note that the players can collide with ground and enemies, but not with other players.
|
||||
* player1Shape.collisionMask = ENEMY | GROUND;
|
||||
* player2Shape.collisionMask = ENEMY | GROUND;
|
||||
* enemyShape .collisionMask = PLAYER | GROUND;
|
||||
* groundShape .collisionMask = PLAYER | ENEMY;
|
||||
*
|
||||
* @example
|
||||
* // How collision check is done
|
||||
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
|
||||
* // The shapes will collide
|
||||
* }
|
||||
*/
|
||||
* Collision group that this shape belongs to (bit mask). See <a href="http://www.aurelienribon.com/blog/2011/07/box2d-tutorial-collision-filtering/">this tutorial</a>.
|
||||
* @property collisionGroup
|
||||
* @type {Number}
|
||||
* @example
|
||||
* // Setup bits for each available group
|
||||
* var PLAYER = Math.pow(2,0),
|
||||
* ENEMY = Math.pow(2,1),
|
||||
* GROUND = Math.pow(2,2)
|
||||
*
|
||||
* // Put shapes into their groups
|
||||
* player1Shape.collisionGroup = PLAYER;
|
||||
* player2Shape.collisionGroup = PLAYER;
|
||||
* enemyShape .collisionGroup = ENEMY;
|
||||
* groundShape .collisionGroup = GROUND;
|
||||
*
|
||||
* // Assign groups that each shape collide with.
|
||||
* // Note that the players can collide with ground and enemies, but not with other players.
|
||||
* player1Shape.collisionMask = ENEMY | GROUND;
|
||||
* player2Shape.collisionMask = ENEMY | GROUND;
|
||||
* enemyShape .collisionMask = PLAYER | GROUND;
|
||||
* groundShape .collisionMask = PLAYER | ENEMY;
|
||||
*
|
||||
* @example
|
||||
* // How collision check is done
|
||||
* if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
|
||||
* // The shapes will collide
|
||||
* }
|
||||
*/
|
||||
this.collisionGroup = 1;
|
||||
|
||||
/**
|
||||
* Collision mask of this shape. See .collisionGroup.
|
||||
* @property collisionMask
|
||||
* @type {Number}
|
||||
*/
|
||||
* Collision mask of this shape. See .collisionGroup.
|
||||
* @property collisionMask
|
||||
* @type {Number}
|
||||
*/
|
||||
this.collisionMask = 1;
|
||||
if(type) this.updateBoundingRadius();
|
||||
|
||||
/**
|
||||
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
|
||||
* @property material
|
||||
* @type {Material}
|
||||
*/
|
||||
* Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead.
|
||||
* @property material
|
||||
* @type {Material}
|
||||
*/
|
||||
this.material = null;
|
||||
|
||||
/**
|
||||
* Area of this shape.
|
||||
* @property area
|
||||
* @type {Number}
|
||||
*/
|
||||
this.area = 0;
|
||||
|
||||
this.updateArea();
|
||||
};
|
||||
|
||||
Shape.CIRCLE = 1;
|
||||
|
@ -88,3 +97,11 @@ Shape.prototype.computeMomentOfInertia = function(mass){
|
|||
Shape.prototype.updateBoundingRadius = function(){
|
||||
throw new Error("Shape.updateBoundingRadius is not implemented in this Shape...");
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the .area property of the shape.
|
||||
* @method updateArea
|
||||
*/
|
||||
Shape.prototype.updateArea = function(){
|
||||
// To be implemented in all subclasses
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
var vec2 = require('../math/vec2'),
|
||||
Solver = require('./Solver');
|
||||
var vec2 = require('../math/vec2')
|
||||
, Solver = require('./Solver')
|
||||
, Utils = require('../utils/Utils')
|
||||
, FrictionEquation = require('../constraints/FrictionEquation')
|
||||
|
||||
module.exports = GSSolver;
|
||||
|
||||
var ARRAY_TYPE = Float32Array || Array;
|
||||
|
||||
/**
|
||||
* Iterative Gauss-Seidel constraint equation solver.
|
||||
*
|
||||
|
@ -19,59 +19,66 @@ var ARRAY_TYPE = Float32Array || Array;
|
|||
* @param {Number} options.tolerance
|
||||
*/
|
||||
function GSSolver(options){
|
||||
Solver.call(this);
|
||||
Solver.call(this,options);
|
||||
options = options || {};
|
||||
this.iterations = options.iterations || 10;
|
||||
this.tolerance = options.tolerance || 0;
|
||||
this.debug = options.debug || false;
|
||||
this.arrayStep = 30;
|
||||
this.lambda = new ARRAY_TYPE(this.arrayStep);
|
||||
this.Bs = new ARRAY_TYPE(this.arrayStep);
|
||||
this.invCs = new ARRAY_TYPE(this.arrayStep);
|
||||
|
||||
/**
|
||||
* Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
|
||||
* @type {Boolean}
|
||||
* @property useGlobalEquationParameters
|
||||
*/
|
||||
* The number of iterations to do when solving. More gives better results, but is more expensive.
|
||||
* @property iterations
|
||||
* @type {Number}
|
||||
*/
|
||||
this.iterations = options.iterations || 10;
|
||||
|
||||
/**
|
||||
* The error tolerance. If the total error is below this limit, the solver will stop. Set to zero for as good solution as possible.
|
||||
* @property tolerance
|
||||
* @type {Number}
|
||||
*/
|
||||
this.tolerance = options.tolerance || 0;
|
||||
|
||||
this.debug = options.debug || false;
|
||||
this.arrayStep = 30;
|
||||
this.lambda = new Utils.ARRAY_TYPE(this.arrayStep);
|
||||
this.Bs = new Utils.ARRAY_TYPE(this.arrayStep);
|
||||
this.invCs = new Utils.ARRAY_TYPE(this.arrayStep);
|
||||
|
||||
/**
|
||||
* Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually.
|
||||
* @type {Boolean}
|
||||
* @property useGlobalEquationParameters
|
||||
*/
|
||||
this.useGlobalEquationParameters = true;
|
||||
|
||||
/**
|
||||
* Global equation stiffness.
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
* Global equation stiffness. Larger number gives harder contacts, etc, but may also be more expensive to compute, or it will make your simulation explode.
|
||||
* @property stiffness
|
||||
* @type {Number}
|
||||
*/
|
||||
this.stiffness = 1e6;
|
||||
|
||||
/**
|
||||
* Global equation relaxation.
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
* Global equation relaxation. This is the number of timesteps required for a constraint to be resolved. Larger number will give softer contacts. Set to around 3 or 4 for good enough results.
|
||||
* @property relaxation
|
||||
* @type {Number}
|
||||
*/
|
||||
this.relaxation = 4;
|
||||
|
||||
/**
|
||||
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
|
||||
* @property useZeroRHS
|
||||
* @type {Boolean}
|
||||
*/
|
||||
* Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications.
|
||||
* @property useZeroRHS
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.useZeroRHS = false;
|
||||
|
||||
/**
|
||||
* Number of friction iterations to skip. If .skipFrictionIterations=2, then no FrictionEquations will be iterated until the third iteration.
|
||||
* @property skipFrictionIterations
|
||||
* @type {Number}
|
||||
*/
|
||||
this.skipFrictionIterations = 2;
|
||||
};
|
||||
GSSolver.prototype = new Solver();
|
||||
|
||||
/**
|
||||
* Set stiffness parameters
|
||||
*
|
||||
* @method setSpookParams
|
||||
* @param {number} k
|
||||
* @param {number} d
|
||||
* @deprecated
|
||||
*/
|
||||
GSSolver.prototype.setSpookParams = function(k,d){
|
||||
this.stiffness = k;
|
||||
this.relaxation = d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Solve the system of equations
|
||||
* @method solve
|
||||
|
@ -79,8 +86,12 @@ GSSolver.prototype.setSpookParams = function(k,d){
|
|||
* @param {World} world World to solve
|
||||
*/
|
||||
GSSolver.prototype.solve = function(dt,world){
|
||||
|
||||
this.sortEquations();
|
||||
|
||||
var iter = 0,
|
||||
maxIter = this.iterations,
|
||||
skipFrictionIter = this.skipFrictionIterations,
|
||||
tolSquared = this.tolerance*this.tolerance,
|
||||
equations = this.equations,
|
||||
Neq = equations.length,
|
||||
|
@ -99,9 +110,9 @@ GSSolver.prototype.solve = function(dt,world){
|
|||
|
||||
// Things that does not change during iteration can be computed once
|
||||
if(this.lambda.length < Neq){
|
||||
this.lambda = new ARRAY_TYPE(Neq + this.arrayStep);
|
||||
this.Bs = new ARRAY_TYPE(Neq + this.arrayStep);
|
||||
this.invCs = new ARRAY_TYPE(Neq + this.arrayStep);
|
||||
this.lambda = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
|
||||
this.Bs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
|
||||
this.invCs = new Utils.ARRAY_TYPE(Neq + this.arrayStep);
|
||||
}
|
||||
var invCs = this.invCs,
|
||||
Bs = this.Bs,
|
||||
|
@ -130,9 +141,7 @@ GSSolver.prototype.solve = function(dt,world){
|
|||
|
||||
// Reset vlambda
|
||||
for(i=0; i!==Nbodies; i++){
|
||||
var b=bodies[i], vlambda=b.vlambda;
|
||||
set(vlambda,0,0);
|
||||
b.wlambda = 0;
|
||||
bodies[i].resetConstraintVelocity();
|
||||
}
|
||||
|
||||
// Iterate over equations
|
||||
|
@ -142,49 +151,59 @@ GSSolver.prototype.solve = function(dt,world){
|
|||
deltalambdaTot = 0.0;
|
||||
|
||||
for(j=0; j!==Neq; j++){
|
||||
|
||||
c = equations[j];
|
||||
|
||||
if(c instanceof FrictionEquation && iter < skipFrictionIter)
|
||||
continue;
|
||||
|
||||
var _eps = useGlobalParams ? eps : c.eps;
|
||||
|
||||
// Compute iteration
|
||||
maxForce = c.maxForce;
|
||||
minForce = c.minForce;
|
||||
|
||||
B = Bs[j];
|
||||
invC = invCs[j];
|
||||
lambdaj = lambda[j];
|
||||
GWlambda = c.computeGWlambda(_eps);
|
||||
|
||||
if(useZeroRHS) B = 0;
|
||||
|
||||
deltalambda = invC * ( B - GWlambda - _eps * lambdaj );
|
||||
|
||||
// Clamp if we are not within the min/max interval
|
||||
lambdaj_plus_deltalambda = lambdaj + deltalambda;
|
||||
if(lambdaj_plus_deltalambda < minForce){
|
||||
deltalambda = minForce - lambdaj;
|
||||
} else if(lambdaj_plus_deltalambda > maxForce){
|
||||
deltalambda = maxForce - lambdaj;
|
||||
}
|
||||
lambda[j] += deltalambda;
|
||||
|
||||
deltalambdaTot += Math.abs(deltalambda);
|
||||
|
||||
c.addToWlambda(deltalambda);
|
||||
var deltalambda = GSSolver.iterateEquation(j,c,_eps,Bs,invCs,lambda,useZeroRHS,dt);
|
||||
if(tolSquared !== 0) deltalambdaTot += Math.abs(deltalambda);
|
||||
}
|
||||
|
||||
// If the total error is small enough - stop iterate
|
||||
if(deltalambdaTot*deltalambdaTot <= tolSquared) break;
|
||||
if(tolSquared !== 0 && deltalambdaTot*deltalambdaTot <= tolSquared) break;
|
||||
}
|
||||
|
||||
// Add result to velocity
|
||||
for(i=0; i!==Nbodies; i++){
|
||||
var b=bodies[i], v=b.velocity;
|
||||
add( v, v, b.vlambda);
|
||||
b.angularVelocity += b.wlambda;
|
||||
bodies[i].addConstraintVelocity();
|
||||
}
|
||||
}
|
||||
errorTot = deltalambdaTot;
|
||||
};
|
||||
|
||||
GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt){
|
||||
// Compute iteration
|
||||
var B = Bs[j],
|
||||
invC = invCs[j],
|
||||
lambdaj = lambda[j],
|
||||
GWlambda = eq.computeGWlambda(eps);
|
||||
|
||||
if(eq instanceof FrictionEquation){
|
||||
// Rescale the max friction force according to the normal force
|
||||
eq.maxForce = eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
|
||||
eq.minForce = -eq.contactEquation.multiplier * eq.frictionCoefficient * dt;
|
||||
}
|
||||
|
||||
var maxForce = eq.maxForce,
|
||||
minForce = eq.minForce;
|
||||
|
||||
if(useZeroRHS) B = 0;
|
||||
|
||||
var deltalambda = invC * ( B - GWlambda - eps * lambdaj );
|
||||
|
||||
// Clamp if we are not within the min/max interval
|
||||
var lambdaj_plus_deltalambda = lambdaj + deltalambda;
|
||||
if(lambdaj_plus_deltalambda < minForce){
|
||||
deltalambda = minForce - lambdaj;
|
||||
} else if(lambdaj_plus_deltalambda > maxForce){
|
||||
deltalambda = maxForce - lambdaj;
|
||||
}
|
||||
lambda[j] += deltalambda;
|
||||
eq.multiplier = lambda[j] / dt;
|
||||
eq.addToWlambda(deltalambda);
|
||||
|
||||
return deltalambda;
|
||||
};
|
||||
|
|
|
@ -8,17 +8,17 @@ module.exports = Island;
|
|||
function Island(){
|
||||
|
||||
/**
|
||||
* Current equations in this island.
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
* Current equations in this island.
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.equations = [];
|
||||
|
||||
/**
|
||||
* Current bodies in this island.
|
||||
* @property bodies
|
||||
* @type {Array}
|
||||
*/
|
||||
* Current bodies in this island.
|
||||
* @property bodies
|
||||
* @type {Array}
|
||||
*/
|
||||
this.bodies = [];
|
||||
}
|
||||
|
||||
|
|
|
@ -12,30 +12,41 @@ module.exports = IslandSolver;
|
|||
* @class IslandSolver
|
||||
* @constructor
|
||||
* @param {Solver} subsolver
|
||||
* @param {Object} options
|
||||
* @extends Solver
|
||||
*/
|
||||
function IslandSolver(subsolver){
|
||||
Solver.call(this);
|
||||
function IslandSolver(subsolver,options){
|
||||
Solver.call(this,options);
|
||||
var that = this;
|
||||
|
||||
/**
|
||||
* The solver used in the workers.
|
||||
* @property subsolver
|
||||
* @type {Solver}
|
||||
*/
|
||||
* The solver used in the workers.
|
||||
* @property subsolver
|
||||
* @type {Solver}
|
||||
*/
|
||||
this.subsolver = subsolver;
|
||||
|
||||
/**
|
||||
* Number of islands
|
||||
* @property numIslands
|
||||
* @type {number}
|
||||
*/
|
||||
* Number of islands. Read only.
|
||||
* @property numIslands
|
||||
* @type {number}
|
||||
*/
|
||||
this.numIslands = 0;
|
||||
|
||||
// Pooling of node objects saves some GC load
|
||||
this._nodePool = [];
|
||||
|
||||
/**
|
||||
* Fires before an island is solved.
|
||||
* @event beforeSolveIsland
|
||||
* @param {Island} island
|
||||
*/
|
||||
this.beforeSolveIslandEvent = {
|
||||
type : "beforeSolveIsland",
|
||||
island : null,
|
||||
};
|
||||
};
|
||||
IslandSolver.prototype = new Object(Solver.prototype);
|
||||
IslandSolver.prototype = new Solver();
|
||||
|
||||
function getUnvisitedNode(nodes){
|
||||
var Nnodes = nodes.length;
|
||||
|
@ -153,7 +164,11 @@ IslandSolver.prototype.solve = function(dt,world){
|
|||
this.numIslands = n;
|
||||
|
||||
// Solve islands
|
||||
var e = this.beforeSolveIslandEvent;
|
||||
for(var i=0; i<islands.length; i++){
|
||||
islands[i].solve(dt,this.subsolver);
|
||||
var island = islands[i];
|
||||
e.island = island;
|
||||
this.emit(e);
|
||||
island.solve(dt,this.subsolver);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var Utils = require('../utils/Utils');
|
||||
var Utils = require('../utils/Utils')
|
||||
, EventEmitter = require('../events/EventEmitter')
|
||||
|
||||
module.exports = Solver;
|
||||
|
||||
|
@ -6,22 +7,49 @@ module.exports = Solver;
|
|||
* Base class for constraint solvers.
|
||||
* @class Solver
|
||||
* @constructor
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
function Solver(){
|
||||
function Solver(options){
|
||||
options = options || {};
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
/**
|
||||
* Current equations in the solver.
|
||||
*
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
* Current equations in the solver.
|
||||
*
|
||||
* @property equations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.equations = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that is used to sort all equations before each solve.
|
||||
* @property equationSortFunction
|
||||
* @type {function|boolean}
|
||||
*/
|
||||
this.equationSortFunction = options.equationSortFunction || false;
|
||||
};
|
||||
Solver.prototype = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Method to be implemented in each subclass
|
||||
* @method solve
|
||||
* @param {Number} dt
|
||||
* @param {World} world
|
||||
*/
|
||||
Solver.prototype.solve = function(dt,world){
|
||||
throw new Error("Solver.solve should be implemented by subclasses!");
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort all equations using the .equationSortFunction. Should be called by subclasses before solving.
|
||||
* @method sortEquations
|
||||
*/
|
||||
Solver.prototype.sortEquations = function(){
|
||||
if(this.equationSortFunction)
|
||||
this.equations.sort(this.equationSortFunction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an equation to be solved.
|
||||
*
|
||||
|
|
|
@ -23,3 +23,11 @@ Utils.appendArray = function(a,b){
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The array type to use for internal numeric computations.
|
||||
* @type {Array}
|
||||
* @static
|
||||
* @property ARRAY_TYPE
|
||||
*/
|
||||
Utils.ARRAY_TYPE = Float32Array || Array;
|
||||
|
|
|
@ -200,7 +200,7 @@ Phaser.Physics.Arcade.prototype = {
|
|||
* @param {number} velocity - Any component of velocity (e.g. 20).
|
||||
* @param {number} acceleration - Rate at which the velocity is changing.
|
||||
* @param {number} drag - Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set.
|
||||
* @param {number} mMax - An absolute value cap for the velocity.
|
||||
* @param {number} [max=10000] - An absolute value cap for the velocity.
|
||||
* @return {number} The altered Velocity value.
|
||||
*/
|
||||
computeVelocity: function (axis, body, velocity, acceleration, drag, max) {
|
||||
|
|
|
@ -392,11 +392,10 @@ Phaser.Physics.Arcade.Body.prototype = {
|
|||
this.checkWorldBounds();
|
||||
}
|
||||
|
||||
this.hull.setTo(this.preX, this.preY, this.width, this.height);
|
||||
|
||||
// this.hull.setTo(this.preX, this.preY, this.width, this.height);
|
||||
// this.hullX.setTo(this.x, this.preY, this.width, this.height);
|
||||
// this.hullY.setTo(this.preX, this.y, this.width, this.height);
|
||||
this.updateHulls(true);
|
||||
// this.updateHulls(true);
|
||||
}
|
||||
|
||||
if (this.skipQuadTree === false && this.allowCollision.none === false && this.sprite.visible && this.sprite.alive)
|
||||
|
|
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part2.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part3.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part4.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part5.png
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part6.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part7.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part8.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
tutorials/03 Using Phaser with TypeScript/screen shots/part9.png
Normal file
After Width: | Height: | Size: 1.9 KiB |