0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2025-03-14 00:38:41 -04:00
This commit is contained in:
Brice 2019-01-24 15:44:14 +00:00 committed by GitHub
commit fc39e81f81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 563 additions and 711 deletions

View file

@ -41,6 +41,8 @@ var Axes = require('../geometry/Axes');
id: Common.nextId(),
type: 'body',
label: 'Body',
pairs: [],
region: null,
parts: [],
plugin: {},
angle: 0,

View file

@ -17,6 +17,7 @@ var Events = require('../core/Events');
var Common = require('../core/Common');
var Bounds = require('../geometry/Bounds');
var Body = require('./Body');
var Grid = require('../collision/Grid');
(function() {
@ -28,17 +29,22 @@ var Body = require('./Body');
* @return {composite} A new composite
*/
Composite.create = function(options) {
return Common.extend({
var composite = Common.extend({
id: Common.nextId(),
type: 'composite',
parent: null,
isModified: false,
bodies: [],
constraints: [],
bodies: [],
constraints: [],
composites: [],
allBodies: [],
label: 'Composite',
plugin: {}
}, options);
composite.allBodies = composite.bodies.slice();
return composite;
};
/**
@ -164,6 +170,7 @@ var Body = require('./Body');
compositeA.composites.push(compositeB);
compositeB.parent = compositeA;
Composite.setModified(compositeA, true, true, false);
Composite.addBodies(compositeA, compositeB.allBodies);
return compositeA;
};
@ -179,8 +186,9 @@ var Body = require('./Body');
Composite.removeComposite = function(compositeA, compositeB, deep) {
var position = Common.indexOf(compositeA.composites, compositeB);
if (position !== -1) {
Composite.removeCompositeAt(compositeA, position);
compositeA.composites.splice(position, 1);
Composite.setModified(compositeA, true, true, false);
Composite.removeBodies(compositeA, compositeB.allBodies);
}
if (deep) {
@ -192,20 +200,6 @@ var Body = require('./Body');
return compositeA;
};
/**
* Removes a composite from the given composite.
* @private
* @method removeCompositeAt
* @param {composite} composite
* @param {number} position
* @return {composite} The original composite with the composite removed
*/
Composite.removeCompositeAt = function(composite, position) {
composite.composites.splice(position, 1);
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Adds a body to the given composite.
* @private
@ -217,6 +211,8 @@ var Body = require('./Body');
Composite.addBody = function(composite, body) {
composite.bodies.push(body);
Composite.setModified(composite, true, true, false);
Composite.addBodies(composite, [body]);
return composite;
};
@ -232,8 +228,9 @@ var Body = require('./Body');
Composite.removeBody = function(composite, body, deep) {
var position = Common.indexOf(composite.bodies, body);
if (position !== -1) {
Composite.removeBodyAt(composite, position);
composite.bodies.splice(position, 1);
Composite.setModified(composite, true, true, false);
Composite.removeBodies(composite, [body]);
}
if (deep) {
@ -246,17 +243,49 @@ var Body = require('./Body');
};
/**
* Removes a body from the given composite.
* Adds a list of bodies directly or indirectly belonging to the composite
* @private
* @method removeBodyAt
* @method addBodies
* @param {composite} composite
* @param {number} position
* @return {composite} The original composite with the body removed
* @param {bodies} bodies
*/
Composite.removeBodyAt = function(composite, position) {
composite.bodies.splice(position, 1);
Composite.setModified(composite, true, true, false);
return composite;
Composite.addBodies = function (composite, bodies) {
Array.prototype.push.apply(composite.allBodies, bodies);
composite.allBodies.sort(function (bodyA, bodyB) {
return bodyA.id - bodyB.id;
});
if (composite.grid) {
Grid.addBodies(composite.grid, bodies, composite);
}
if (composite.parent && composite.parent.type === 'composite') {
composite.parent.addBodies(composite.parent, bodies);
}
};
/**
* Removes a list of bodies directly or indirectly belonging to the composite
* @private
* @method removeBodies
* @param {composite} composite
* @param {bodies} bodies
*/
Composite.removeBodies = function (composite, bodies) {
var allBodies = composite.allBodies;
for (var i = 0; i < bodies.length; i += 1) {
var position = allBodies.indexOf(bodies[i]);
if (position !== -1)
allBodies.splice(position, 1);
}
if (composite.grid) {
Grid.removeBodies(composite.grid, bodies);
}
if (composite.parent && composite.parent.type === 'composite') {
composite.parent.removeBodies(composite.parent, bodies);
}
};
/**
@ -346,12 +375,7 @@ var Body = require('./Body');
* @return {body[]} All the bodies
*/
Composite.allBodies = function(composite) {
var bodies = [].concat(composite.bodies);
for (var i = 0; i < composite.composites.length; i++)
bodies = bodies.concat(Composite.allBodies(composite.composites[i]));
return bodies;
return composite.allBodies;
};
/**

View file

@ -34,6 +34,7 @@ var Common = require('../core/Common');
var defaults = {
label: 'World',
grid: null,
gravity: {
x: 0,
y: 1,

View file

@ -12,73 +12,118 @@ module.exports = Detector;
var SAT = require('./SAT');
var Pair = require('./Pair');
var Pairs = require('./Pairs');
var Sleeping = require('../core/Sleeping');
var Bounds = require('../geometry/Bounds');
var dummyPair = { idA: 1 << 30, idB: (1 << 30) + 1 };
(function() {
/**
* Finds all collisions given a list of pairs.
* Finds all collisions given a list of bodies and their potential collision pairs
* @method collisions
* @param {pair[]} broadphasePairs
* @param {bodies[]} bodies
* @param {engine} engine
* @return {array} collisions
*/
Detector.collisions = function(broadphasePairs, engine) {
var collisions = [],
pairsTable = engine.pairs.table;
Detector.collisions = function(bodies, engine) {
var allPairs = engine.pairs,
oldPairs = allPairs.list,
newPairs = [],
collisionStart,
collisionActive,
collisionEnd;
// @if DEBUG
var metrics = engine.metrics;
// @endif
for (var i = 0; i < broadphasePairs.length; i++) {
var bodyA = broadphasePairs[i][0],
bodyB = broadphasePairs[i][1];
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
var engineEvents = engine.events;
var hasCollisionEvent = !!(
engineEvents.collisionStart ||
engineEvents.collisionActive ||
engineEvents.collisionEnd
);
// @if DEBUG
metrics.midphaseTests += 1;
// @endif
if (hasCollisionEvent) {
collisionStart = [];
collisionActive = [];
collisionEnd = [];
allPairs.collisionStart = collisionStart;
allPairs.collisionActive = collisionActive;
allPairs.collisionEnd = collisionEnd;
}
// mid phase
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
for (var j = bodyA.parts.length > 1 ? 1 : 0; j < bodyA.parts.length; j++) {
var partA = bodyA.parts[j];
oldPairs.push(dummyPair);
for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) {
var partB = bodyB.parts[k];
var pairIndex = 0,
oldPair = oldPairs[pairIndex++];
if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
// find a previous collision we could reuse
var pairId = Pair.id(partA, partB),
pair = pairsTable[pairId],
previousCollision;
for (var i = 0; i < bodies.length; i++) {
var bodyA = bodies[i],
partsA = bodyA.parts,
firstPartsA = bodyA.parts.length > 1 ? 1 : 0,
pairs = bodyA.pairs;
if (pair && pair.isActive) {
previousCollision = pair.collision;
} else {
previousCollision = null;
}
for (var j = 0; j < pairs.length; j++) {
var bodyB = pairs[j][0];
// narrow phase
var collision = SAT.collides(partA, partB, previousCollision);
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
// @if DEBUG
metrics.narrowphaseTests += 1;
if (collision.reused)
metrics.narrowReuseCount += 1;
// @endif
// @if DEBUG
metrics.midphaseTests += 1;
// @endif
// mid phase
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
for (var l = firstPartsA; l < partsA.length; l++) {
var partA = partsA[l];
var partsB = bodyB.parts;
for (var k = partsB.length > 1 ? 1 : 0; k < partsB.length; k++) {
var partB = partsB[k];
if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
// narrow phase
var collision = SAT.collides(partA, partB);
if (collision.collided) {
collisions.push(collision);
// @if DEBUG
metrics.narrowDetections += 1;
metrics.narrowphaseTests += 1;
// @endif
if (collision) {
var newPair = Pair.create(collision);
newPairs.push(newPair);
if (hasCollisionEvent) {
// Check old pairs to determine which collisions are new
// and which collisions are not active anymore
var idA = collision.bodyA.id;
var idB = collision.bodyB.id;
while (oldPair.idA < idA || (oldPair.idA === idA && oldPair.idB < idB)) {
collisionEnd.push(oldPair);
oldPair = oldPairs[pairIndex++];
}
if (oldPair.idA === idA && oldPair.idB === idB) {
// Pair was already active
collisionActive.push(newPair);
oldPair = oldPairs[pairIndex++];
} else {
// Pair could not be found, collision is new
collisionStart.push(newPair);
}
}
// @if DEBUG
metrics.narrowDetections += 1;
// @endif
}
}
}
}
@ -86,7 +131,14 @@ var Bounds = require('../geometry/Bounds');
}
}
return collisions;
if (hasCollisionEvent) {
pairIndex -= 1;
while (pairIndex < oldPairs.length - 1) {
collisionEnd.push(oldPairs[pairIndex++]);
}
}
allPairs.list = newPairs;
};
/**

View file

@ -8,8 +8,6 @@ var Grid = {};
module.exports = Grid;
var Pair = require('./Pair');
var Detector = require('./Detector');
var Common = require('../core/Common');
(function() {
@ -23,10 +21,7 @@ var Common = require('../core/Common');
Grid.create = function(options) {
var defaults = {
controller: Grid,
detector: Detector.collisions,
buckets: {},
pairs: {},
pairsList: [],
buckets: [],
bucketWidth: 48,
bucketHeight: 48
};
@ -56,15 +51,19 @@ var Common = require('../core/Common');
* @param {grid} grid
* @param {body[]} bodies
* @param {engine} engine
* @param {boolean} forceUpdate
*/
Grid.update = function(grid, bodies, engine, forceUpdate) {
Grid.update = function(grid, bodies, engine) {
var i, col, row,
world = engine.world,
buckets = grid.buckets,
bucket,
bucketId,
gridChanged = false;
world = engine.world,
worldMinX = world.bounds.min.x,
worldMaxX = world.bounds.max.x,
worldMinY = world.bounds.min.y,
worldMaxY = world.bounds.max.y,
worldBounded = isFinite(worldMinX) ||
isFinite(worldMaxX) ||
isFinite(worldMinY) ||
isFinite(worldMaxY);
// @if DEBUG
var metrics = engine.metrics;
@ -74,97 +73,126 @@ var Common = require('../core/Common');
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (body.isSleeping && !forceUpdate)
if (body.isSleeping)
continue;
// don't update out of world bodies
if (body.bounds.max.x < world.bounds.min.x || body.bounds.min.x > world.bounds.max.x
|| body.bounds.max.y < world.bounds.min.y || body.bounds.min.y > world.bounds.max.y)
var bounds = body.bounds;
if (worldBounded && (bounds.max.x < worldMinX || bounds.min.x > worldMaxX
|| bounds.max.y < worldMinY || bounds.min.y > worldMaxY))
continue;
var newRegion = Grid._getRegion(grid, body);
var newRegion = Grid._getRegion(grid, bounds);
var oldRegion = body.region;
// set the new region
body.region = newRegion;
// if the body has changed grid region
if (!body.region || newRegion.id !== body.region.id || forceUpdate) {
if (
newRegion.startCol === oldRegion.startCol &&
newRegion.endCol === oldRegion.endCol &&
newRegion.startRow === oldRegion.startRow &&
newRegion.endRow === oldRegion.endRow
) {
continue;
}
// @if DEBUG
metrics.broadphaseTests += 1;
// @endif
// @if DEBUG
metrics.broadphaseTests += 1;
// @endif
if (!body.region || forceUpdate)
body.region = newRegion;
var startCol = Math.min(newRegion.startCol, oldRegion.startCol),
endCol = Math.max(newRegion.endCol, oldRegion.endCol),
startRow = Math.min(newRegion.startRow, oldRegion.startRow),
endRow = Math.max(newRegion.endRow, oldRegion.endRow);
var union = Grid._regionUnion(newRegion, body.region);
// update grid buckets affected by region change
// iterate over the union of both regions
for (col = startCol; col <= endCol; col++) {
var isInsideNewColumn = (col >= newRegion.startCol && col <= newRegion.endCol);
var isInsideOldColumn = (col >= oldRegion.startCol && col <= oldRegion.endCol);
// update grid buckets affected by region change
// iterate over the union of both regions
for (col = union.startCol; col <= union.endCol; col++) {
for (row = union.startRow; row <= union.endRow; row++) {
bucketId = Grid._getBucketId(col, row);
bucket = buckets[bucketId];
for (row = startRow; row <= endRow; row++) {
var isInsideNewRegion = isInsideNewColumn && (row >= newRegion.startRow && row <= newRegion.endRow);
var isInsideOldRegion = isInsideOldColumn && (row >= oldRegion.startRow && row <= oldRegion.endRow);
var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol
&& row >= newRegion.startRow && row <= newRegion.endRow);
var isInsideOldRegion = (col >= body.region.startCol && col <= body.region.endCol
&& row >= body.region.startRow && row <= body.region.endRow);
// remove from old region buckets
if (!isInsideNewRegion && isInsideOldRegion) {
if (isInsideOldRegion) {
if (bucket)
Grid._bucketRemoveBody(grid, bucket, body);
}
}
// add to new region buckets
if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) {
if (!bucket)
bucket = Grid._createBucket(buckets, bucketId);
Grid._bucketAddBody(grid, bucket, body);
// remove from old region buckets
if (isInsideOldRegion) {
if (!isInsideNewRegion) {
Grid._bucketRemoveBody(grid, body, buckets[col][row]);
}
} else if (isInsideNewRegion) {
Grid._bucketAddBody(grid, body, buckets, col, row);
}
}
// set the new region
body.region = newRegion;
// flag changes so we can update pairs
gridChanged = true;
}
}
// update pairs list only if pairs changed (i.e. a body changed region)
if (gridChanged)
grid.pairsList = Grid._createActivePairsList(grid);
};
/**
* Clears the grid.
* @method clear
* @param {grid} grid
*/
Grid.clear = function(grid) {
grid.buckets = {};
grid.pairs = {};
grid.pairsList = [];
Grid.clear = function(grid, bodies) {
grid.buckets = [];
for (var i = 0; i < bodies.length; i++) {
bodies.pairs.length = 0;
}
};
/**
* Finds the union of two regions.
* @method _regionUnion
* @private
* @param {} regionA
* @param {} regionB
* @return {} region
*/
Grid._regionUnion = function(regionA, regionB) {
var startCol = Math.min(regionA.startCol, regionB.startCol),
endCol = Math.max(regionA.endCol, regionB.endCol),
startRow = Math.min(regionA.startRow, regionB.startRow),
endRow = Math.max(regionA.endRow, regionB.endRow);
Grid.removeBodies = function (grid, bodies) {
var i, col, row,
buckets = grid.buckets;
return Grid._createRegion(startCol, endCol, startRow, endRow);
for (i = 0; i < bodies.length; i++) {
var body = bodies[i],
region = body.region,
startCol = region.startCol,
endCol = region.endCol,
startRow = region.startRow,
endRow = region.endRow;
for (col = startCol; col <= endCol; col++) {
for (row = startRow; row <= endRow; row++) {
Grid._bucketRemoveBody(grid, body, buckets[col][row]);
}
}
body.region = null;
body.pairs.length = 0;
}
};
Grid.addBodies = function(grid, bodies, world) {
var i, col, row,
buckets = grid.buckets,
worldMinX = world.bounds.min.x,
worldMaxX = world.bounds.max.x,
worldMinY = world.bounds.min.y,
worldMaxY = world.bounds.max.y,
worldBounded = isFinite(worldMinX) ||
isFinite(worldMaxX) ||
isFinite(worldMinY) ||
isFinite(worldMaxY);
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
// don't update out of world bodies
var bounds = body.bounds;
if (worldBounded && (bounds.max.x < worldMinX || bounds.min.x > worldMaxX
|| bounds.max.y < worldMinY || bounds.min.y > worldMaxY))
continue;
var newRegion = Grid._getRegion(grid, bounds);
// set the new region
body.region = newRegion;
// update grid buckets affected by region change
// iterate over the union of both regions
for (col = newRegion.startCol; col <= newRegion.endCol; col++) {
for (row = newRegion.startRow; row <= newRegion.endRow; row++) {
Grid._bucketAddBody(grid, body, buckets, col, row);
}
}
}
};
/**
@ -175,61 +203,15 @@ var Common = require('../core/Common');
* @param {} body
* @return {} region
*/
Grid._getRegion = function(grid, body) {
var bounds = body.bounds,
startCol = Math.floor(bounds.min.x / grid.bucketWidth),
endCol = Math.floor(bounds.max.x / grid.bucketWidth),
startRow = Math.floor(bounds.min.y / grid.bucketHeight),
endRow = Math.floor(bounds.max.y / grid.bucketHeight);
return Grid._createRegion(startCol, endCol, startRow, endRow);
};
/**
* Creates a region.
* @method _createRegion
* @private
* @param {} startCol
* @param {} endCol
* @param {} startRow
* @param {} endRow
* @return {} region
*/
Grid._createRegion = function(startCol, endCol, startRow, endRow) {
Grid._getRegion = function(grid, bounds) {
return {
id: startCol + ',' + endCol + ',' + startRow + ',' + endRow,
startCol: startCol,
endCol: endCol,
startRow: startRow,
endRow: endRow
startCol: Math.floor(bounds.min.x / grid.bucketWidth),
endCol: Math.floor(bounds.max.x / grid.bucketWidth),
startRow: Math.floor(bounds.min.y / grid.bucketHeight),
endRow: Math.floor(bounds.max.y / grid.bucketHeight)
};
};
/**
* Gets the bucket id at the given position.
* @method _getBucketId
* @private
* @param {} column
* @param {} row
* @return {string} bucket id
*/
Grid._getBucketId = function(column, row) {
return 'C' + column + 'R' + row;
};
/**
* Creates a bucket.
* @method _createBucket
* @private
* @param {} buckets
* @param {} bucketId
* @return {} bucket
*/
Grid._createBucket = function(buckets, bucketId) {
var bucket = buckets[bucketId] = [];
return bucket;
};
/**
* Adds a body to a bucket.
* @method _bucketAddBody
@ -238,8 +220,12 @@ var Common = require('../core/Common');
* @param {} bucket
* @param {} body
*/
Grid._bucketAddBody = function(grid, bucket, body) {
Grid._bucketAddBody = function(grid, body, buckets, col, row) {
// add new pairs
var bucketCol = buckets[col] || (buckets[col] = []),
bucket = bucketCol[row] || (bucketCol[row] = []);
var bodyA;
for (var i = 0; i < bucket.length; i++) {
var bodyB = bucket[i];
@ -248,14 +234,28 @@ var Common = require('../core/Common');
// keep track of the number of buckets the pair exists in
// important for Grid.update to work
var pairId = Pair.id(body, bodyB),
pair = grid.pairs[pairId];
if (pair) {
pair[2] += 1;
if (body.id < bodyB.id) {
bodyA = body;
} else {
grid.pairs[pairId] = [body, bodyB, 1];
bodyA = bodyB;
bodyB = body;
}
// TODO: work with linked list for improved performance?
var pairs = bodyA.pairs;
for (var p = 0; p < pairs.length; p += 1) {
var pair = pairs[p];
if (pair[0] === bodyB) {
pair[1] += 1;
break;
} else if (pair[1].id > bodyB.id) {
pairs.splice(p, 0, [bodyB, 1]);
break;
}
}
if (p === pairs.length)
pairs.push([bodyB, 1]);
}
// add to bodies (after pairs, otherwise pairs with self)
@ -270,52 +270,36 @@ var Common = require('../core/Common');
* @param {} bucket
* @param {} body
*/
Grid._bucketRemoveBody = function(grid, bucket, body) {
Grid._bucketRemoveBody = function(grid, body, bucket) {
// remove from bucket
bucket.splice(Common.indexOf(bucket, body), 1);
// update pair counts
var bodyA;
for (var i = 0; i < bucket.length; i++) {
// keep track of the number of buckets the pair exists in
// important for _createActivePairsList to work
var bodyB = bucket[i],
pairId = Pair.id(body, bodyB),
pair = grid.pairs[pairId];
if (pair)
pair[2] -= 1;
}
};
/**
* Generates a list of the active pairs in the grid.
* @method _createActivePairsList
* @private
* @param {} grid
* @return [] pairs
*/
Grid._createActivePairsList = function(grid) {
var pairKeys,
pair,
pairs = [];
// grid.pairs is used as a hashmap
pairKeys = Common.keys(grid.pairs);
// iterate over grid.pairs
for (var k = 0; k < pairKeys.length; k++) {
pair = grid.pairs[pairKeys[k]];
// if pair exists in at least one bucket
// it is a pair that needs further collision testing so push it
if (pair[2] > 0) {
pairs.push(pair);
// important for Grid.update to work
var bodyB = bucket[i];
if (body.id < bodyB.id) {
bodyA = body;
} else {
delete grid.pairs[pairKeys[k]];
bodyA = bodyB;
bodyB = body;
}
var pairs = bodyA.pairs;
for (var p = 0; p < pairs.length; p += 1) {
var pair = pairs[p];
if (pair[0] === bodyB) {
if (pair[1] === 1) {
pairs.splice(p, 1);
} else {
pair[1] -= 1;
}
break;
}
}
}
return pairs;
};
})();

View file

@ -17,104 +17,30 @@ module.exports = Pair;
* @param {number} timestamp
* @return {pair} A new pair
*/
Pair.create = function(collision, timestamp) {
Pair.create = function(collision) {
var bodyA = collision.bodyA,
bodyB = collision.bodyB;
bodyB = collision.bodyB,
parentA = collision.parentA,
parentB = collision.parentB;
var pair = {
id: Pair.id(bodyA, bodyB),
idA: bodyA.id,
idB: bodyB.id,
bodyA: bodyA,
bodyB: bodyB,
activeContacts: [],
separation: 0,
isActive: true,
confirmedActive: true,
activeContacts: collision.supports,
activeContactsCount: collision.supportCount,
separation: collision.depth,
isSensor: bodyA.isSensor || bodyB.isSensor,
timeCreated: timestamp,
timeUpdated: timestamp,
collision: null,
inverseMass: 0,
friction: 0,
frictionStatic: 0,
restitution: 0,
slop: 0
collision: collision,
inverseMass: parentA.inverseMass + parentB.inverseMass,
friction: Math.min(parentA.friction, parentB.friction),
frictionStatic: Math.max(parentA.frictionStatic, parentB.frictionStatic),
restitution: Math.max(parentA.restitution, parentB.restitution),
slop: Math.max(parentA.slop, parentB.slop)
};
Pair.update(pair, collision, timestamp);
return pair;
};
/**
* Updates a pair given a collision.
* @method update
* @param {pair} pair
* @param {collision} collision
* @param {number} timestamp
*/
Pair.update = function(pair, collision, timestamp) {
pair.collision = collision;
if (collision.collided) {
var supports = collision.supports,
activeContacts = pair.activeContacts,
parentA = collision.parentA,
parentB = collision.parentB;
pair.inverseMass = parentA.inverseMass + parentB.inverseMass;
pair.friction = Math.min(parentA.friction, parentB.friction);
pair.frictionStatic = Math.max(parentA.frictionStatic, parentB.frictionStatic);
pair.restitution = Math.max(parentA.restitution, parentB.restitution);
pair.slop = Math.max(parentA.slop, parentB.slop);
for (var i = 0; i < supports.length; i++) {
activeContacts[i] = supports[i].contact;
}
// optimise array size
var supportCount = supports.length;
if (supportCount < activeContacts.length) {
activeContacts.length = supportCount;
}
pair.separation = collision.depth;
Pair.setActive(pair, true, timestamp);
} else {
if (pair.isActive === true)
Pair.setActive(pair, false, timestamp);
}
};
/**
* Set a pair as active or inactive.
* @method setActive
* @param {pair} pair
* @param {bool} isActive
* @param {number} timestamp
*/
Pair.setActive = function(pair, isActive, timestamp) {
if (isActive) {
pair.isActive = true;
pair.timeUpdated = timestamp;
} else {
pair.isActive = false;
pair.activeContacts.length = 0;
}
};
/**
* Get the id for the given pair.
* @method id
* @param {body} bodyA
* @param {body} bodyB
* @return {string} Unique pairId
*/
Pair.id = function(bodyA, bodyB) {
if (bodyA.id < bodyB.id) {
return 'A' + bodyA.id + 'B' + bodyB.id;
} else {
return 'A' + bodyB.id + 'B' + bodyA.id;
}
};
})();

View file

@ -8,7 +8,6 @@ var Pairs = {};
module.exports = Pairs;
var Pair = require('./Pair');
var Common = require('../core/Common');
(function() {
@ -22,8 +21,7 @@ var Common = require('../core/Common');
* @return {pairs} A new pairs structure
*/
Pairs.create = function(options) {
return Common.extend({
table: {},
return Common.extend({
list: [],
collisionStart: [],
collisionActive: [],
@ -31,116 +29,6 @@ var Common = require('../core/Common');
}, options);
};
/**
* Updates pairs given a list of collisions.
* @method update
* @param {object} pairs
* @param {collision[]} collisions
* @param {number} timestamp
*/
Pairs.update = function(pairs, collisions, timestamp) {
var pairsList = pairs.list,
pairsTable = pairs.table,
collisionStart = pairs.collisionStart,
collisionEnd = pairs.collisionEnd,
collisionActive = pairs.collisionActive,
collision,
pairId,
pair,
i;
// clear collision state arrays, but maintain old reference
collisionStart.length = 0;
collisionEnd.length = 0;
collisionActive.length = 0;
for (i = 0; i < pairsList.length; i++) {
pairsList[i].confirmedActive = false;
}
for (i = 0; i < collisions.length; i++) {
collision = collisions[i];
if (collision.collided) {
pairId = Pair.id(collision.bodyA, collision.bodyB);
pair = pairsTable[pairId];
if (pair) {
// pair already exists (but may or may not be active)
if (pair.isActive) {
// pair exists and is active
collisionActive.push(pair);
} else {
// pair exists but was inactive, so a collision has just started again
collisionStart.push(pair);
}
// update the pair
Pair.update(pair, collision, timestamp);
pair.confirmedActive = true;
} else {
// pair did not exist, create a new pair
pair = Pair.create(collision, timestamp);
pairsTable[pairId] = pair;
// push the new pair
collisionStart.push(pair);
pairsList.push(pair);
}
}
}
// deactivate previously active pairs that are now inactive
for (i = 0; i < pairsList.length; i++) {
pair = pairsList[i];
if (pair.isActive && !pair.confirmedActive) {
Pair.setActive(pair, false, timestamp);
collisionEnd.push(pair);
}
}
};
/**
* Finds and removes pairs that have been inactive for a set amount of time.
* @method removeOld
* @param {object} pairs
* @param {number} timestamp
*/
Pairs.removeOld = function(pairs, timestamp) {
var pairsList = pairs.list,
pairsTable = pairs.table,
indexesToRemove = [],
pair,
collision,
pairIndex,
i;
for (i = 0; i < pairsList.length; i++) {
pair = pairsList[i];
collision = pair.collision;
// never remove sleeping pairs
if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) {
pair.timeUpdated = timestamp;
continue;
}
// if pair is inactive for too long, mark it to be removed
if (timestamp - pair.timeUpdated > Pairs._pairMaxIdleLife) {
indexesToRemove.push(i);
}
}
// remove marked pairs
for (i = 0; i < indexesToRemove.length; i++) {
pairIndex = indexesToRemove[i] - i;
pair = pairsList[pairIndex];
delete pairsTable[pair.id];
pairsList.splice(pairIndex, 1);
}
};
/**
* Clears the given pairs structure.
* @method clear
@ -148,7 +36,6 @@ var Common = require('../core/Common');
* @return {pairs} pairs
*/
Pairs.clear = function(pairs) {
pairs.table = {};
pairs.list.length = 0;
pairs.collisionStart.length = 0;
pairs.collisionActive.length = 0;

View file

@ -38,7 +38,7 @@ var Vertices = require('../geometry/Vertices');
if (Bounds.overlaps(part.bounds, body.bounds)) {
var collision = SAT.collides(part, body);
if (collision.collided) {
if (collision) {
collisions.push(collision);
break;
}

View file

@ -34,11 +34,8 @@ var Bounds = require('../geometry/Bounds');
// find total contacts on each body
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
activeCount = pair.activeContacts.length;
activeCount = pair.activeContactsCount;
pair.collision.parentA.totalContacts += activeCount;
pair.collision.parentB.totalContacts += activeCount;
}
@ -80,7 +77,7 @@ var Bounds = require('../geometry/Bounds');
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive || pair.isSensor)
if (pair.isSensor)
continue;
collision = pair.collision;
@ -170,13 +167,13 @@ var Bounds = require('../geometry/Bounds');
j,
pair,
contacts,
contactsCount,
collision,
bodyA,
bodyB,
normal,
tangent,
contact,
contactVertex,
normalImpulse,
tangentImpulse,
offset,
@ -186,10 +183,11 @@ var Bounds = require('../geometry/Bounds');
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive || pair.isSensor)
if (pair.isSensor)
continue;
contacts = pair.activeContacts;
contactsCount = pair.activeContactsCount;
collision = pair.collision;
bodyA = collision.parentA;
bodyB = collision.parentB;
@ -197,9 +195,8 @@ var Bounds = require('../geometry/Bounds');
tangent = collision.tangent;
// resolve each contact
for (j = 0; j < contacts.length; j++) {
for (j = 0; j < contactsCount; j++) {
contact = contacts[j];
contactVertex = contact.vertex;
normalImpulse = contact.normalImpulse;
tangentImpulse = contact.tangentImpulse;
@ -210,14 +207,14 @@ var Bounds = require('../geometry/Bounds');
// apply impulse from contact
if (!(bodyA.isStatic || bodyA.isSleeping)) {
offset = Vector.sub(contactVertex, bodyA.position, tempA);
offset = Vector.sub(contact, bodyA.position, tempA);
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia;
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
offset = Vector.sub(contactVertex, bodyB.position, tempA);
offset = Vector.sub(contact, bodyB.position, tempA);
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia;
@ -235,53 +232,74 @@ var Bounds = require('../geometry/Bounds');
*/
Resolver.solveVelocity = function(pairs, timeScale) {
var timeScaleSquared = timeScale * timeScale,
impulse = Vector._temp[0],
tempA = Vector._temp[1],
tempB = Vector._temp[2],
tempC = Vector._temp[3],
tempD = Vector._temp[4],
tempE = Vector._temp[5];
restingThresh = Resolver._restingThresh,
restingThreshTangent = Resolver._restingThreshTangent,
frictionNormalMultiplier = Resolver._frictionNormalMultiplier;
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
if (!pair.isActive || pair.isSensor)
if (pair.isSensor)
continue;
var collision = pair.collision,
bodyA = collision.parentA,
bodyB = collision.parentB,
normal = collision.normal,
normalX = normal.x,
normalY = normal.y,
tangent = collision.tangent,
tangentX = tangent.x,
tangentY = tangent.y,
contacts = pair.activeContacts,
contactsCount = pair.activeContactsCount,
contactShare = 1 / contacts.length;
// update body velocities
bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x;
bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y;
bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x;
bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y;
bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev;
bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev;
var positionA = bodyA.position,
positionAX = positionA.x,
positionAY = positionA.y,
positionB = bodyB.position,
positionBX = positionB.x,
positionBY = positionB.y,
velocityAX = positionAX - bodyA.positionPrev.x,
velocityAY = positionAY - bodyA.positionPrev.y,
velocityBX = positionBX - bodyB.positionPrev.x,
velocityBY = positionBY - bodyB.positionPrev.y,
angularVelocityA = bodyA.angle - bodyA.anglePrev,
angularVelocityB = bodyB.angle - bodyB.anglePrev;
bodyA.velocity.x = velocityAX;
bodyA.velocity.y = velocityAY;
bodyB.velocity.x = velocityBX;
bodyB.velocity.y = velocityBY;
bodyA.angularVelocity = angularVelocityA;
bodyB.angularVelocity = angularVelocityB;
var inverseMassA = bodyA.inverseMass,
inverseMassB = bodyB.inverseMass,
inverseInertiaA = bodyA.inverseInertia,
inverseInertiaB = bodyB.inverseInertia;
// resolve each contact
for (var j = 0; j < contacts.length; j++) {
for (var j = 0; j < contactsCount; j++) {
var contact = contacts[j],
contactVertex = contact.vertex,
offsetA = Vector.sub(contactVertex, bodyA.position, tempA),
offsetB = Vector.sub(contactVertex, bodyB.position, tempB),
velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC),
velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD),
relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE),
normalVelocity = Vector.dot(normal, relativeVelocity);
offsetAX = contact.x - positionAX,
offsetAY = contact.y - positionAY,
offsetBX = contact.x - positionBX,
offsetBY = contact.y - positionBY,
relativeVelocityX = (velocityAX - offsetAY * angularVelocityA) - (velocityBX - offsetBY * angularVelocityB),
relativeVelocityY = (velocityAY + offsetAX * angularVelocityA) - (velocityBY + offsetBX * angularVelocityB),
normalVelocity = normalX * relativeVelocityX + normalY * relativeVelocityY;
var tangentVelocity = Vector.dot(tangent, relativeVelocity),
var tangentVelocity = tangentX * relativeVelocityX + tangentY * relativeVelocityY,
tangentSpeed = Math.abs(tangentVelocity),
tangentVelocityDirection = Common.sign(tangentVelocity);
// raw impulses
var normalImpulse = (1 + pair.restitution) * normalVelocity,
normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier;
normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * frictionNormalMultiplier;
// coulomb friction
var tangentImpulse = tangentVelocity,
@ -296,15 +314,15 @@ var Bounds = require('../geometry/Bounds');
}
// modify impulses accounting for mass, inertia and offset
var oAcN = Vector.cross(offsetA, normal),
oBcN = Vector.cross(offsetB, normal),
share = contactShare / (bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN);
var oAcN = offsetAX * normalY - offsetAY * normalX,
oBcN = offsetBX * normalY - offsetBY * normalX,
share = contactShare / (inverseMassA + inverseMassB + inverseInertiaA * oAcN * oAcN + inverseInertiaB * oBcN * oBcN);
normalImpulse *= share;
tangentImpulse *= share;
// handle high velocity and resting collisions separately
if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) {
if (normalVelocity < 0 && normalVelocity * normalVelocity > restingThresh * timeScaleSquared) {
// high normal velocity so clear cached contact normal impulse
contact.normalImpulse = 0;
} else {
@ -316,7 +334,7 @@ var Bounds = require('../geometry/Bounds');
}
// handle high velocity and resting collisions separately
if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) {
if (tangentVelocity * tangentVelocity > restingThreshTangent * timeScaleSquared) {
// high tangent velocity so clear cached contact tangent impulse
contact.tangentImpulse = 0;
} else {
@ -328,20 +346,20 @@ var Bounds = require('../geometry/Bounds');
}
// total impulse from contact
impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse);
impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse);
var impulseX = (normalX * normalImpulse) + (tangentX * tangentImpulse);
var impulseY = (normalY * normalImpulse) + (tangentY * tangentImpulse);
// apply impulse from contact
if (!(bodyA.isStatic || bodyA.isSleeping)) {
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia;
bodyA.positionPrev.x += impulseX * inverseMassA;
bodyA.positionPrev.y += impulseY * inverseMassA;
bodyA.anglePrev += (offsetAX * impulseY - offsetAY * impulseX) * inverseInertiaA;
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia;
bodyB.positionPrev.x -= impulseX * inverseMassB;
bodyB.positionPrev.y -= impulseY * inverseMassB;
bodyB.anglePrev -= (offsetBX * impulseY - offsetBY * impulseX) * inverseInertiaB;
}
}
}

View file

@ -20,134 +20,95 @@ var Vector = require('../geometry/Vector');
* @method collides
* @param {body} bodyA
* @param {body} bodyB
* @param {collision} previousCollision
* @return {collision} collision
*/
SAT.collides = function(bodyA, bodyB, previousCollision) {
var overlapAB,
overlapBA,
minOverlap,
collision,
canReusePrevCol = false;
if (previousCollision) {
// estimate total motion
var parentA = bodyA.parent,
parentB = bodyB.parent,
motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed
+ parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed;
// we may be able to (partially) reuse collision result
// but only safe if collision was resting
canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2;
// reuse collision object
collision = previousCollision;
} else {
collision = { collided: false, bodyA: bodyA, bodyB: bodyB };
SAT.collides = function(bodyA, bodyB) {
var overlapAB = SAT._overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes);
if (overlapAB.overlap <= 0) {
return null;
}
if (previousCollision && canReusePrevCol) {
// if we can reuse the collision result
// we only need to test the previously found axis
var axisBodyA = collision.axisBody,
axisBodyB = axisBodyA === bodyA ? bodyB : bodyA,
axes = [axisBodyA.axes[previousCollision.axisNumber]];
minOverlap = SAT._overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes);
collision.reused = true;
if (minOverlap.overlap <= 0) {
collision.collided = false;
return collision;
}
} else {
// if we can't reuse a result, perform a full SAT test
overlapAB = SAT._overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes);
if (overlapAB.overlap <= 0) {
collision.collided = false;
return collision;
}
overlapBA = SAT._overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes);
if (overlapBA.overlap <= 0) {
collision.collided = false;
return collision;
}
if (overlapAB.overlap < overlapBA.overlap) {
minOverlap = overlapAB;
collision.axisBody = bodyA;
} else {
minOverlap = overlapBA;
collision.axisBody = bodyB;
}
// important for reuse later
collision.axisNumber = minOverlap.axisNumber;
var overlapBA = SAT._overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes);
if (overlapBA.overlap <= 0) {
return null;
}
collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
collision.collided = true;
collision.depth = minOverlap.overlap;
collision.parentA = collision.bodyA.parent;
collision.parentB = collision.bodyB.parent;
bodyA = collision.bodyA;
bodyB = collision.bodyB;
var minOverlap = (overlapAB.overlap < overlapBA.overlap) ? overlapAB : overlapBA;
// ensure normal is facing away from bodyA
if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) {
collision.normal = {
x: minOverlap.axis.x,
y: minOverlap.axis.y
var positionA = bodyA.position,
positionB = bodyB.position,
axis = minOverlap.axis,
normal;
if (axis.x * (positionB.x - positionA.x) + axis.y * (positionB.y - positionA.y) < 0) {
normal = {
x: axis.x,
y: axis.y
};
} else {
collision.normal = {
x: -minOverlap.axis.x,
y: -minOverlap.axis.y
normal = {
x: -axis.x,
y: -axis.y
};
}
collision.tangent = Vector.perp(collision.normal);
collision.penetration = collision.penetration || {};
collision.penetration.x = collision.normal.x * collision.depth;
collision.penetration.y = collision.normal.y * collision.depth;
// find support points, there is always either exactly one or two
var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal),
supports = [];
var verticesA = bodyA.vertices,
verticesB = bodyB.vertices,
potentialSupportsB = SAT._findSupports(bodyA, verticesB, normal),
supportCount = 0,
supports = new Array(2);
// find the supports from bodyB that are inside bodyA
if (Vertices.contains(bodyA.vertices, verticesB[0]))
supports.push(verticesB[0]);
if (Vertices.contains(bodyA.vertices, verticesB[1]))
supports.push(verticesB[1]);
// find the supports from bodyA that are inside bodyB
if (supports.length < 2) {
var verticesA = SAT._findSupports(bodyB, bodyA, Vector.neg(collision.normal));
if (Vertices.contains(bodyB.vertices, verticesA[0]))
supports.push(verticesA[0]);
if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1]))
supports.push(verticesA[1]);
if (Vertices.contains(verticesA, potentialSupportsB[0])) {
supports[supportCount++] = potentialSupportsB[0];
}
// account for the edge case of overlapping but no vertex containment
if (supports.length < 1)
supports = [verticesB[0]];
collision.supports = supports;
if (Vertices.contains(verticesA, potentialSupportsB[1]))
supports[supportCount++] = potentialSupportsB[1];
return collision;
// find the supports from bodyA that are inside bodyB
if (supportCount < 2) {
var potentialSupportsA = SAT._findSupports(bodyB, verticesA, Vector.neg(normal));
if (Vertices.contains(verticesB, potentialSupportsA[0]))
supports[supportCount++] = potentialSupportsA[0];
if (supportCount < 2) {
if (Vertices.contains(verticesB, potentialSupportsA[1]))
supports[supportCount++] = potentialSupportsA[1];
if (supportCount < 2) {
supports[1] = null;
// account for the edge case of overlapping but no vertex containment
if (supportCount < 1)
supports[supportCount++] = potentialSupportsB[0];
}
}
}
var depth = minOverlap.overlap;
return {
bodyA: bodyA,
bodyB: bodyB,
depth: depth,
parentA: bodyA.parent,
parentB: bodyB.parent,
normal: normal,
tangent: {
x: -normal.y,
y: normal.x
},
penetration: {
x: normal.x * depth,
y: normal.y * depth
},
supports: supports,
supportCount: supportCount
};
};
/**
@ -182,7 +143,6 @@ var Vector = require('../geometry/Vector');
if (overlap < result.overlap) {
result.overlap = overlap;
result.axis = axis;
result.axisNumber = i;
}
}
@ -216,30 +176,32 @@ var Vector = require('../geometry/Vector');
};
/**
* Finds supporting vertices given two bodies along a given direction using hill-climbing.
* Finds supporting vertices given a body and a set of vertices along a given direction using hill-climbing.
* @method _findSupports
* @private
* @param {} bodyA
* @param {} bodyB
* @param {} body
* @param {} vertices
* @param {} normal
* @return [vector]
*/
SAT._findSupports = function(bodyA, bodyB, normal) {
SAT._findSupports = function(body, vertices, normal) {
var nearestDistance = Number.MAX_VALUE,
vertexToBody = Vector._temp[0],
vertices = bodyB.vertices,
bodyAPosition = bodyA.position,
vertexToBodyX,
vertexToBodyY,
position = body.position,
positionX = position.x,
positionY = position.y,
distance,
vertex,
vertexA,
vertexB;
// find closest vertex on bodyB
// find closest vertex
for (var i = 0; i < vertices.length; i++) {
vertex = vertices[i];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
distance = -Vector.dot(normal, vertexToBody);
vertexToBodyX = vertex.x - positionX;
vertexToBodyY = vertex.y - positionY;
distance = -(normal.x * vertexToBodyX + normal.y * vertexToBodyY);
if (distance < nearestDistance) {
nearestDistance = distance;
@ -250,16 +212,16 @@ var Vector = require('../geometry/Vector');
// find next closest vertex using the two connected to it
var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1;
vertex = vertices[prevIndex];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
nearestDistance = -Vector.dot(normal, vertexToBody);
vertexToBodyX = vertex.x - positionX;
vertexToBodyY = vertex.y - positionY;
nearestDistance = -(normal.x * vertexToBodyX + normal.y * vertexToBodyY);
vertexB = vertex;
var nextIndex = (vertexA.index + 1) % vertices.length;
vertex = vertices[nextIndex];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
distance = -Vector.dot(normal, vertexToBody);
vertexToBodyX = vertex.x - positionX;
vertexToBodyY = vertex.y - positionY;
distance = -(normal.x * vertexToBodyX + normal.y * vertexToBodyY);
if (distance < nearestDistance) {
vertexB = vertex;
}

View file

@ -265,9 +265,8 @@ var Common = require('../core/Common');
var body = bodies[i],
impulse = body.constraintImpulse;
if (body.isStatic || (impulse.x === 0 && impulse.y === 0 && impulse.angle === 0)) {
if (body.isStatic || (impulse.x === 0 && impulse.y === 0 && impulse.angle === 0))
continue;
}
Sleeping.set(body, false);

View file

@ -19,6 +19,7 @@ var Render = require('../render/Render');
var Pairs = require('../collision/Pairs');
var Metrics = require('./Metrics');
var Grid = require('../collision/Grid');
var Detector = require('../collision/Detector');
var Events = require('./Events');
var Composite = require('../body/Composite');
var Constraint = require('../constraint/Constraint');
@ -88,6 +89,8 @@ var Body = require('../body/Body');
engine.broadphase = engine.broadphase.controller.create(engine.broadphase);
engine.metrics = engine.metrics || { extended: false };
engine.world.grid = engine.broadphase;
// @if DEBUG
engine.metrics = Metrics.create(engine.metrics);
// @endif
@ -159,15 +162,11 @@ var Body = require('../body/Body');
// broadphase pass: find potential collision pairs
if (broadphase.controller) {
// if world is dirty, we must flush the whole grid
if (world.isModified)
broadphase.controller.clear(broadphase);
// if (world.isModified)
// broadphase.controller.reset(broadphase, allBodies, engine);
// update the grid buckets based on current bodies
broadphase.controller.update(broadphase, allBodies, engine, world.isModified);
broadphasePairs = broadphase.pairsList;
} else {
// if no broadphase set, we just pass all bodies
broadphasePairs = allBodies;
broadphase.controller.update(broadphase, allBodies, engine);
}
// clear all composite modified flags
@ -176,13 +175,11 @@ var Body = require('../body/Body');
}
// narrowphase pass: find actual collisions, then create or update collision pairs
var collisions = broadphase.detector(broadphasePairs, engine);
// var timestamp = timing.timestamp;
Detector.collisions(allBodies, engine);
// update collision pairs
var pairs = engine.pairs,
timestamp = timing.timestamp;
Pairs.update(pairs, collisions, timestamp);
Pairs.removeOld(pairs, timestamp);
var pairs = engine.pairs;
// wake up bodies involved in collisions
if (engine.enableSleeping)
@ -263,14 +260,13 @@ var Body = require('../body/Body');
*/
Engine.clear = function(engine) {
var world = engine.world;
Pairs.clear(engine.pairs);
var broadphase = engine.broadphase;
if (broadphase.controller) {
var bodies = Composite.allBodies(world);
broadphase.controller.clear(broadphase);
broadphase.controller.update(broadphase, bodies, engine, true);
broadphase.controller.clear(broadphase, bodies);
}
};

View file

@ -24,8 +24,6 @@ var Common = require('./Common');
extended: false,
narrowDetections: 0,
narrowphaseTests: 0,
narrowReuse: 0,
narrowReuseCount: 0,
midphaseTests: 0,
broadphaseTests: 0,
narrowEff: 0.0001,
@ -50,8 +48,6 @@ var Common = require('./Common');
if (metrics.extended) {
metrics.narrowDetections = 0;
metrics.narrowphaseTests = 0;
metrics.narrowReuse = 0;
metrics.narrowReuseCount = 0;
metrics.midphaseTests = 0;
metrics.broadphaseTests = 0;
metrics.narrowEff = 0;
@ -82,7 +78,6 @@ var Common = require('./Common');
metrics.midEff = (metrics.narrowDetections / (metrics.midphaseTests || 1)).toFixed(2);
metrics.narrowEff = (metrics.narrowDetections / (metrics.narrowphaseTests || 1)).toFixed(2);
metrics.broadEff = (1 - (metrics.broadphaseTests / (bodies.length || 1))).toFixed(2);
metrics.narrowReuse = (metrics.narrowReuseCount / (metrics.narrowphaseTests || 1)).toFixed(2);
//var broadphase = engine.broadphase[engine.broadphase.current];
//if (broadphase.instance)
// metrics.buckets = Common.keys(broadphase.instance.buckets).length;

View file

@ -65,24 +65,26 @@ var Events = require('./Events');
// wake up bodies involved in collisions
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
// don't wake inactive pairs
if (!pair.isActive)
continue;
var collision = pair.collision,
bodyA = collision.bodyA.parent,
bodyB = collision.bodyB.parent;
// don't wake if at least one body is static
if ((bodyA.isSleeping && bodyB.isSleeping) || bodyA.isStatic || bodyB.isStatic)
continue;
if (bodyA.isSleeping || bodyB.isSleeping) {
var sleepingBody = (bodyA.isSleeping && !bodyA.isStatic) ? bodyA : bodyB,
movingBody = sleepingBody === bodyA ? bodyB : bodyA;
if (!sleepingBody.isStatic && movingBody.motion > Sleeping._motionWakeThreshold * timeFactor) {
if (bodyA.isSleeping || bodyB.isSleeping) {
var sleepingBody, movingBody;
if (bodyA.isSleeping) {
sleepingBody = bodyA;
movingBody = bodyB;
} else {
sleepingBody = bodyB;
movingBody = bodyA;
}
if (movingBody.motion > Sleeping._motionWakeThreshold * timeFactor) {
Sleeping.set(sleepingBody, false);
}
}

View file

@ -39,20 +39,7 @@ var Common = require('../core/Common');
for (var i = 0; i < points.length; i++) {
var point = points[i],
vertex = {
x: point.x,
y: point.y,
index: i,
body: body,
isInternal: false,
contact: null
};
vertex.contact = {
vertex: vertex,
normalImpulse: 0,
tangentImpulse: 0
};
vertex = Vertices.createVertex(body, i, point.x, point.y);
vertices.push(vertex);
}
@ -60,6 +47,27 @@ var Common = require('../core/Common');
return vertices;
};
/**
* Creates a new vertex
*
* @method create
* @param {body} body
* @param {i} index of vertex within the body
* @param {x} vertex position x
* @param {y} vertex position y
*/
Vertices.createVertex = function(body, i, x, y) {
return {
x: x,
y: y,
index: i,
body: body,
isInternal: false,
normalImpulse: 0,
tangentImpulse: 0
};
};
/**
* Parses a string containing ordered x y pairs separated by spaces (and optionally commas),
* into a `Matter.Vertices` object for the given `Matter.Body`.
@ -226,12 +234,13 @@ var Common = require('../core/Common');
* @return {boolean} True if the vertices contains point, otherwise false
*/
Vertices.contains = function(vertices, point) {
for (var i = 0; i < vertices.length; i++) {
var vertice = vertices[i],
nextVertice = vertices[(i + 1) % vertices.length];
var nextVertice = vertices[0];
for (var i = vertices.length - 1; i >= 0; i--) {
var vertice = vertices[i];
if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) {
return false;
}
nextVertice = vertice;
}
return true;

View file

@ -1082,14 +1082,10 @@ var Mouse = require('../core/Mouse');
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
collision = pair.collision;
for (j = 0; j < pair.activeContacts.length; j++) {
var contact = pair.activeContacts[j],
vertex = contact.vertex;
c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5);
for (j = 0; j < pair.activeContactsCount; j++) {
var contact = pair.activeContacts[j];
c.rect(contact.x - 1.5, contact.y - 1.5, 3.5, 3.5);
}
}
@ -1105,19 +1101,15 @@ var Mouse = require('../core/Mouse');
// render collision normals
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
collision = pair.collision;
if (pair.activeContacts.length > 0) {
var normalPosX = pair.activeContacts[0].vertex.x,
normalPosY = pair.activeContacts[0].vertex.y;
if (pair.activeContactsCount > 0) {
var normalPosX = pair.activeContacts[0].x,
normalPosY = pair.activeContacts[0].y;
if (pair.activeContacts.length === 2) {
normalPosX = (pair.activeContacts[0].vertex.x + pair.activeContacts[1].vertex.x) / 2;
normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2;
if (pair.activeContactsCount === 2) {
normalPosX = (pair.activeContacts[0].x + pair.activeContacts[1].x) / 2;
normalPosY = (pair.activeContacts[0].y + pair.activeContacts[1].y) / 2;
}
if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) {
@ -1165,9 +1157,6 @@ var Mouse = require('../core/Mouse');
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
collision = pair.collision;
bodyA = collision.bodyA;
bodyB = collision.bodyB;
@ -1217,19 +1206,25 @@ var Mouse = require('../core/Mouse');
c.beginPath();
var bucketKeys = Common.keys(grid.buckets);
var buckets = grid.buckets;
var columnKeys = Common.keys(buckets);
for (var i = 0; i < bucketKeys.length; i++) {
var bucketId = bucketKeys[i];
for (var i = 0; i < columnKeys.length; i += 1) {
var columnKey = columnKeys[i];
var column = buckets[columnKey];
var rowKeys = Object.keys(column);
for (var j = 0; j < rowKeys.length; j += 1) {
var rowKey = rowKeys[j];
var bucket = column[rowKey];
if (grid.buckets[bucketId].length < 2)
continue;
if (bucket.length < 2)
continue;
var region = bucketId.split(/C|R/);
c.rect(0.5 + parseInt(region[1], 10) * grid.bucketWidth,
0.5 + parseInt(region[2], 10) * grid.bucketHeight,
grid.bucketWidth,
grid.bucketHeight);
c.rect(0.5 + parseInt(columnKey, 10) * grid.bucketWidth,
0.5 + parseInt(rowKey, 10) * grid.bucketHeight,
grid.bucketWidth,
grid.bucketHeight);
}
}
c.lineWidth = 1;