diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 7b47c9f..a4626e2 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -1,84 +1,133 @@ /** -* The `Matter.Detector` module contains methods for detecting collisions given a set of pairs. +* The `Matter.Detector` module contains methods for efficiently detecting collisions between a list of bodies using a broadphase algorithm. * * @class Detector */ -// TODO: speculative contacts - var Detector = {}; module.exports = Detector; -var SAT = require('./SAT'); -var Pair = require('./Pair'); +var Common = require('../core/Common'); +var Collision = require('./Collision'); (function() { /** - * Finds all collisions given a list of pairs. - * @method collisions - * @param {pair[]} broadphasePairs - * @param {engine} engine - * @return {array} collisions + * Creates a new collision detector. + * @method create + * @param {} options + * @return {detector} A new collision detector */ - Detector.collisions = function(broadphasePairs, engine) { + Detector.create = function(options) { + var defaults = { + bodies: [], + pairs: null + }; + + return Common.extend(defaults, options); + }; + + /** + * Sets the list of bodies in the detector. + * @method setBodies + * @param {detector} detector + * @param {body[]} bodies + */ + Detector.setBodies = function(detector, bodies) { + detector.bodies = bodies.slice(0); + }; + + /** + * Clears the detector including its list of bodies. + * @method clear + * @param {detector} detector + */ + Detector.clear = function(detector) { + detector.bodies = []; + }; + + /** + * Efficiently finds all collisions among all the bodies in `detector.bodies` using a broadphase algorithm. + * + * _Note:_ The specific ordering of collisions returned is not guaranteed between releases and may change for performance reasons. + * If a specific ordering is required then apply a sort to the resulting array. + * @method collisions + * @param {detector} detector + * @return {collision[]} collisions + */ + Detector.collisions = function(detector) { var collisions = [], - pairs = engine.pairs, - broadphasePairsLength = broadphasePairs.length, + pairs = detector.pairs, + bodies = detector.bodies, + bodiesLength = bodies.length, canCollide = Detector.canCollide, - collides = SAT.collides, - i; + collides = Collision.collides, + i, + j; - for (i = 0; i < broadphasePairsLength; i++) { - var broadphasePair = broadphasePairs[i], - bodyA = broadphasePair[0], - bodyB = broadphasePair[1]; + bodies.sort(Detector._compareBoundsX); - if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping)) - continue; - - if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) - continue; + for (i = 0; i < bodiesLength; i++) { + var bodyA = bodies[i], + boundsA = bodyA.bounds, + boundXMax = bodyA.bounds.max.x, + boundYMax = bodyA.bounds.max.y, + boundYMin = bodyA.bounds.min.y, + bodyAStatic = bodyA.isStatic || bodyA.isSleeping, + partsALength = bodyA.parts.length, + partsASingle = partsALength === 1; - var boundsA = bodyA.bounds, - boundsB = bodyB.bounds; + for (j = i + 1; j < bodiesLength; j++) { + var bodyB = bodies[j], + boundsB = bodyB.bounds; - if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x - || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { - continue; - } - - var partsALength = bodyA.parts.length, - partsBLength = bodyB.parts.length; - - if (partsALength === 1 && partsBLength === 1) { - var collision = collides(bodyA, bodyB, pairs); - - if (collision) { - collisions.push(collision); + if (boundsB.min.x > boundXMax) { + break; } - } else { - var partsAStart = partsALength > 1 ? 1 : 0, - partsBStart = partsBLength > 1 ? 1 : 0; - - for (var j = partsAStart; j < partsALength; j++) { - var partA = bodyA.parts[j], - boundsA = partA.bounds; - for (var k = partsBStart; k < partsBLength; k++) { - var partB = bodyB.parts[k], - boundsB = partB.bounds; + if (boundYMax < boundsB.min.y || boundYMin > boundsB.max.y) { + continue; + } - if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x - || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { - continue; - } + if (bodyAStatic && (bodyB.isStatic || bodyB.isSleeping)) { + continue; + } - var collision = collides(partA, partB, pairs); + if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) { + continue; + } - if (collision) { - collisions.push(collision); + var partsBLength = bodyB.parts.length; + + if (partsASingle && partsBLength === 1) { + var collision = collides(bodyA, bodyB, pairs); + + if (collision) { + collisions.push(collision); + } + } else { + var partsAStart = partsALength > 1 ? 1 : 0, + partsBStart = partsBLength > 1 ? 1 : 0; + + for (var k = partsAStart; k < partsALength; k++) { + var partA = bodyA.parts[k], + boundsA = partA.bounds; + + for (var z = partsBStart; z < partsBLength; z++) { + var partB = bodyB.parts[z], + boundsB = partB.bounds; + + if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x + || boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) { + continue; + } + + var collision = collides(partA, partB, pairs); + + if (collision) { + collisions.push(collision); + } } } } @@ -103,4 +152,39 @@ var Pair = require('./Pair'); return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0; }; + /** + * The comparison function used in the broadphase algorithm. + * Returns the signed delta of the bodies bounds on the x-axis. + * @private + * @method _sortCompare + * @param {body} bodyA + * @param {body} bodyB + * @return {number} The signed delta used for sorting + */ + Detector._compareBoundsX = function(bodyA, bodyB) { + return bodyA.bounds.min.x - bodyB.bounds.min.x; + }; + + /* + * + * Properties Documentation + * + */ + + /** + * The array of `Matter.Body` between which the detector finds collisions. + * + * _Note:_ The order of bodies in this array _is not fixed_ and will be continually managed by the detector. + * @property bodies + * @type body[] + * @default [] + */ + + /** + * Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage. + * @property pairs + * @type {pairs|null} + * @default null + */ + })(); diff --git a/src/core/Engine.js b/src/core/Engine.js index bb98b55..7d15e4c 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -16,7 +16,6 @@ var Sleeping = require('./Sleeping'); var Resolver = require('../collision/Resolver'); var Detector = require('../collision/Detector'); var Pairs = require('../collision/Pairs'); -var Grid = require('../collision/Grid'); var Events = require('./Events'); var Composite = require('../body/Composite'); var Constraint = require('../constraint/Constraint'); @@ -43,7 +42,6 @@ var Body = require('../body/Body'); enableSleeping: false, events: [], plugin: {}, - grid: null, gravity: { x: 0, y: 1, @@ -60,10 +58,11 @@ var Body = require('../body/Body'); var engine = Common.extend(defaults, options); engine.world = options.world || Composite.create({ label: 'World' }); - engine.grid = Grid.create(options.grid || options.broadphase); - engine.pairs = Pairs.create(); + engine.pairs = options.pairs || Pairs.create(); + engine.detector = options.detector || Detector.create(); - // temporary back compatibility + // for temporary back compatibility only + engine.grid = { buckets: [] }; engine.world.gravity = engine.gravity; engine.broadphase = engine.grid; engine.metrics = {}; @@ -93,9 +92,10 @@ var Body = require('../body/Body'); correction = correction || 1; var world = engine.world, + detector = engine.detector, + pairs = engine.pairs, timing = engine.timing, - grid = engine.grid, - gridPairs = [], + timestamp = timing.timestamp, i; // increment timestamp @@ -109,15 +109,25 @@ var Body = require('../body/Body'); Events.trigger(engine, 'beforeUpdate', event); - // get lists of all bodies and constraints, no matter what composites they are in + // get all bodies and all constraints in the world var allBodies = Composite.allBodies(world), allConstraints = Composite.allConstraints(world); - // if sleeping enabled, call the sleeping controller + // update the detector bodies if they have changed + if (world.isModified) { + Detector.setBodies(detector, allBodies); + } + + // reset all composite modified flags + if (world.isModified) { + Composite.setModified(world, false, false, true); + } + + // update sleeping if enabled if (engine.enableSleeping) Sleeping.update(allBodies, timing.timeScale); - // applies gravity to all bodies + // apply gravity to all bodies Engine._bodiesApplyGravity(allBodies, engine.gravity); // update all body position and rotation by integration @@ -130,27 +140,11 @@ var Body = require('../body/Body'); } Constraint.postSolveAll(allBodies); - // broadphase pass: find potential collision pairs - - // if world is dirty, we must flush the whole grid - if (world.isModified) - Grid.clear(grid); - - // update the grid buckets based on current bodies - Grid.update(grid, allBodies, engine, world.isModified); - gridPairs = grid.pairsList; - - // clear all composite modified flags - if (world.isModified) { - Composite.setModified(world, false, false, true); - } - - // narrowphase pass: find actual collisions, then create or update collision pairs - var collisions = Detector.collisions(gridPairs, engine); + // find all collisions + detector.pairs = engine.pairs; + var collisions = Detector.collisions(detector); // update collision pairs - var pairs = engine.pairs, - timestamp = timing.timestamp; Pairs.update(pairs, collisions, timestamp); // wake up bodies involved in collisions @@ -224,17 +218,13 @@ var Body = require('../body/Body'); }; /** - * Clears the engine including the world, pairs and broadphase. + * Clears the engine pairs and detector. * @method clear * @param {engine} engine */ Engine.clear = function(engine) { - var world = engine.world, - bodies = Composite.allBodies(world); - Pairs.clear(engine.pairs); - Grid.clear(engine.grid); - Grid.update(engine.grid, bodies, engine, true); + Detector.clear(engine.detector); }; /** @@ -314,53 +304,53 @@ var Body = require('../body/Body'); * Fired just before an update * * @event beforeUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update and all collision events * * @event afterUpdate - * @param {} event An event object + * @param {object} event An event object * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any) * * @event collisionStart - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any) * * @event collisionActive - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /** * Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any) * * @event collisionEnd - * @param {} event An event object - * @param {} event.pairs List of affected pairs + * @param {object} event An event object + * @param {pair[]} event.pairs List of affected pairs * @param {number} event.timestamp The engine.timing.timestamp of the event - * @param {} event.source The source object of the event - * @param {} event.name The name of the event + * @param {engine} event.source The source object of the event + * @param {string} event.name The name of the event */ /* @@ -452,9 +442,18 @@ var Body = require('../body/Body'); * @default 0 */ + /** + * A `Matter.Detector` instance. + * + * @property detector + * @type detector + * @default a Matter.Detector instance + */ + /** * A `Matter.Grid` instance. * + * @deprecated replaced by `engine.detector` * @property grid * @type grid * @default a Matter.Grid instance @@ -463,7 +462,7 @@ var Body = require('../body/Body'); /** * Replaced by and now alias for `engine.grid`. * - * @deprecated use `engine.grid` + * @deprecated replaced by `engine.detector` * @property broadphase * @type grid * @default a Matter.Grid instance