0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2025-03-14 00:38:41 -04:00

Merge commit '076bee9099a029fc9cd2810e8860c29ca044a1c6' into optim-detector-collision

This commit is contained in:
Brice Chevalier 2018-03-24 17:11:15 +09:00
commit 81511fd58f
7 changed files with 225 additions and 227 deletions

View file

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

View file

@ -16,6 +16,7 @@ module.exports = Composite;
var Events = require('../core/Events');
var Common = require('../core/Common');
var Body = require('./Body');
var Grid = require('../collision/Grid');
(function() {
@ -206,6 +207,7 @@ var Body = require('./Body');
composite.bodies.push(body);
Composite.setModified(composite, true, true, false);
Composite.addBodies(composite, [body]);
return composite;
};
@ -248,6 +250,10 @@ var Body = require('./Body');
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);
}
@ -268,6 +274,10 @@ var Body = require('./Body');
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);
}

View file

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

View file

@ -21,15 +21,15 @@ 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 pairs = engine.pairs,
oldPairs = pairs.list,
Detector.collisions = function(bodies, engine) {
var allPairs = engine.pairs,
oldPairs = allPairs.list,
newPairs = [],
collisionStart,
collisionActive,
@ -50,9 +50,9 @@ var dummyPair = { idA: 1 << 30, idB: (1 << 30) + 1 };
collisionStart = [];
collisionActive = [];
collisionEnd = [];
pairs.collisionStart = collisionStart;
pairs.collisionActive = collisionActive;
pairs.collisionEnd = collisionEnd;
allPairs.collisionStart = collisionStart;
allPairs.collisionActive = collisionActive;
allPairs.collisionEnd = collisionEnd;
}
oldPairs.push(dummyPair);
@ -60,63 +60,70 @@ var dummyPair = { idA: 1 << 30, idB: (1 << 30) + 1 };
var pairIndex = 0,
oldPair = oldPairs[pairIndex++];
for (var i = 0; i < broadphasePairs.length; i++) {
var bodyA = broadphasePairs[i][0],
bodyB = broadphasePairs[i][1];
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 ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
for (var j = 0; j < pairs.length; j++) {
var bodyB = pairs[j][0];
// @if DEBUG
metrics.midphaseTests += 1;
// @endif
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
// 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];
// @if DEBUG
metrics.midphaseTests += 1;
// @endif
for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) {
var partB = bodyB.parts[k];
// mid phase
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
for (var l = firstPartsA; l < partsA.length; l++) {
var partA = partsA[l];
if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
// narrow phase
var collision = SAT.collides(partA, partB);
var partsB = bodyB.parts;
for (var k = partsB.length > 1 ? 1 : 0; k < partsB.length; k++) {
var partB = partsB[k];
// @if DEBUG
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 ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
// narrow phase
var collision = SAT.collides(partA, partB);
// @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
}
}
}
}
@ -131,7 +138,7 @@ var dummyPair = { idA: 1 << 30, idB: (1 << 30) + 1 };
}
}
pairs.list = newPairs;
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,126 +51,148 @@ 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;
metrics.broadphaseTests = 0;
// @endif
var pairsList = [];
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
var bounds = body.bounds;
if (bounds.max.x < world.bounds.min.x || bounds.min.x > world.bounds.max.x
|| bounds.max.y < world.bounds.min.y || bounds.min.y > world.bounds.max.y)
continue;
// 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);
var oldRegion = body.region;
var newRegion = Grid._getRegion(grid, bounds);
var oldRegion = body.region;
// if the body has changed grid region
if (
!oldRegion ||
newRegion.startCol !== oldRegion.startCol ||
newRegion.endCol !== oldRegion.endCol ||
newRegion.startRow !== oldRegion.startRow ||
newRegion.endRow !== oldRegion.endRow ||
forceUpdate
) {
// set the new region
body.region = newRegion;
// @if DEBUG
metrics.broadphaseTests += 1;
// @endif
// if the body has changed grid region
if (
newRegion.startCol === oldRegion.startCol &&
newRegion.endCol === oldRegion.endCol &&
newRegion.startRow === oldRegion.startRow &&
newRegion.endRow === oldRegion.endRow
) {
continue;
}
if (!oldRegion || forceUpdate)
oldRegion = newRegion;
// @if DEBUG
metrics.broadphaseTests += 1;
// @endif
var union = Grid._regionUnion(newRegion, oldRegion);
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);
// 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];
// 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);
var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol
&& row >= newRegion.startRow && row <= newRegion.endRow);
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 isInsideOldRegion = (col >= oldRegion.startCol && col <= oldRegion.endCol
&& row >= oldRegion.startRow && row <= oldRegion.endRow);
// remove from old region buckets
if (!isInsideNewRegion && isInsideOldRegion) {
if (bucket)
Grid._bucketRemoveBody(grid, bucket, body);
}
// add to new region buckets
if (oldRegion === 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;
Grid.clear = function(grid, bodies) {
grid.buckets = [];
for (var i = 0; i < bodies.length; i++) {
bodies.pairs.length = 0;
}
};
// flag changes so we can update pairs
gridChanged = true;
Grid.removeBodies = function (grid, bodies) {
var i, col, row,
buckets = grid.buckets;
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]);
}
}
var pairs = body.pairs;
for (var p = 0; p < pairs.length; p += 1)
pairsList.push(pairs[p]);
body.region = null;
body.pairs.length = 0;
}
grid.pairsList = pairsList;
};
/**
* Clears the grid.
* @method clear
* @param {grid} grid
*/
Grid.clear = function(grid) {
grid.buckets = {};
grid.pairs = {};
grid.pairsList = [];
};
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);
/**
* 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);
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;
return Grid._createRegion(startCol, endCol, startRow, endRow);
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);
}
}
}
};
/**
@ -187,58 +204,14 @@ var Common = require('../core/Common');
* @return {} region
*/
Grid._getRegion = function(grid, bounds) {
var 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) {
return {
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
@ -247,8 +220,10 @@ 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++) {
@ -270,17 +245,17 @@ var Common = require('../core/Common');
var pairs = bodyA.pairs;
for (var p = 0; p < pairs.length; p += 1) {
var pair = pairs[p];
if (pair[1] === bodyB) {
pair[2] += 1;
if (pair[0] === bodyB) {
pair[1] += 1;
break;
} else if (pair[1].id > bodyB.id) {
pairs.splice(p, 0, [bodyA, bodyB, 1]);
pairs.splice(p, 0, [bodyB, 1]);
break;
}
}
if (p === pairs.length)
pairs.push([bodyA, bodyB, 1]);
pairs.push([bodyB, 1]);
}
// add to bodies (after pairs, otherwise pairs with self)
@ -295,7 +270,7 @@ 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);
@ -315,11 +290,11 @@ var Common = require('../core/Common');
var pairs = bodyA.pairs;
for (var p = 0; p < pairs.length; p += 1) {
var pair = pairs[p];
if (pair[1] === bodyB) {
if (pair[2] === 1) {
if (pair[0] === bodyB) {
if (pair[1] === 1) {
pairs.splice(p, 1);
} else {
pair[2] -= 1;
pair[1] -= 1;
}
break;
}

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
@ -177,7 +176,7 @@ var Body = require('../body/Body');
// narrowphase pass: find actual collisions, then create or update collision pairs
// var timestamp = timing.timestamp;
broadphase.detector(broadphasePairs, engine);
Detector.collisions(allBodies, engine);
// update collision pairs
var pairs = engine.pairs;
@ -267,8 +266,7 @@ var Body = require('../body/Body');
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

@ -1199,19 +1199,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;