diff --git a/src/collision/Manager.js b/src/collision/Manager.js index 5c6789b..333262e 100644 --- a/src/collision/Manager.js +++ b/src/collision/Manager.js @@ -8,77 +8,112 @@ var Manager = {}; (function() { - var _pairMaxIdleLife = 500; + var _pairMaxIdleLife = 1000; /** * Description * @method updatePairs * @param {object} pairs - * @param {pair[]} pairsList - * @param {pair[]} candidatePairs - * @param {metrics} metrics - * @param {detector} detector - * @return {bool} pairsUpdated flag + * @param {collision[]} collisions */ - Manager.updatePairs = function(pairs, pairsList, candidatePairs, metrics, detector) { - var i; + Manager.updatePairs = function(pairs, collisions) { + var pairsList = pairs.list, + pairsTable = pairs.table, + collisionStart = pairs.collisionStart, + collisionEnd = pairs.collisionEnd, + collisionActive = pairs.collisionActive, + activePairIds = [], + collision, + pairId, + pair, + i; - // first set all pairs inactive - for (i = 0; i < pairsList.length; i++) { - var pair = pairsList[i]; - Pair.setActive(pair, false); - } - - // detect collisions in current step - var pairsUpdated = false, - collisions = detector(candidatePairs, metrics); + // clear collision state arrays, but maintain old reference + collisionStart.length = 0; + collisionEnd.length = 0; + collisionActive.length = 0; - // set collision pairs to active, or create if pair is new for (i = 0; i < collisions.length; i++) { - var collision = collisions[i], + collision = collisions[i]; + + if (collision.collided) { pairId = Pair.id(collision.bodyA, collision.bodyB); - - if (pairId in pairs) { - Pair.update(pairs[pairId], collision); - } else { - pairs[pairId] = Pair.create(collision); - pairsUpdated = true; + activePairIds.push(pairId); + + if (pairId in pairsTable) { + // pair already exists (but may or may not be active) + pair = pairsTable[pairId]; + + 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); + } else { + // pair did not exist, create a new pair + pair = Pair.create(collision); + 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 && activePairIds.indexOf(pair.id) === -1) { + Pair.setActive(pair, false); + collisionEnd.push(pair); } } - - return pairsUpdated; }; /** * Description * @method removeOldPairs * @param {object} pairs - * @param {pair[]} pairsList - * @return {bool} pairsRemoved flag */ - Manager.removeOldPairs = function(pairs, pairsList) { - var timeNow = Common.now(), - pairsRemoved = false, + Manager.removeOldPairs = function(pairs) { + var pairsList = pairs.list, + pairsTable = pairs.table, + timeNow = Common.now(), + indexesToRemove = [], + pair, + collision, + pairIndex, i; - + for (i = 0; i < pairsList.length; i++) { - var pair = pairsList[i], - collision = pair.collision; + pair = pairsList[i]; + collision = pair.collision; // never remove sleeping pairs if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) { - pair.timestamp = timeNow; + pair.timeUpdated = timeNow; continue; } - // if pair is inactive for too long, remove it - if (timeNow - pair.timestamp > _pairMaxIdleLife) { - delete pairs[pair.id]; - pairsRemoved = true; + // if pair is inactive for too long, mark it to be removed + if (timeNow - pair.timeUpdated > _pairMaxIdleLife) { + indexesToRemove.push(i); } } - - return pairsRemoved; + + // remove marked pairs + for (i = 0; i < indexesToRemove.length; i++) { + pairIndex = indexesToRemove[i]; + pair = pairsList[pairIndex]; + delete pairsTable[pair.id]; + pairsList.splice(pairIndex, 1); + } }; })(); \ No newline at end of file diff --git a/src/collision/Pair.js b/src/collision/Pair.js index 7eadd84..a8ad105 100644 --- a/src/collision/Pair.js +++ b/src/collision/Pair.js @@ -16,15 +16,19 @@ var Pair = {}; */ Pair.create = function(collision) { var bodyA = collision.bodyA, - bodyB = collision.bodyB; + bodyB = collision.bodyB, + timestamp = Common.now(); var pair = { id: Pair.id(bodyA, bodyB), + bodyA: bodyA, + bodyB: bodyB, contacts: {}, activeContacts: [], separation: 0, isActive: true, - timestamp: Common.now(), + timeCreated: timestamp, + timeUpdated: timestamp, inverseMass: bodyA.inverseMass + bodyB.inverseMass, friction: Math.min(bodyA.friction, bodyB.friction), restitution: Math.max(bodyA.restitution, bodyB.restitution), @@ -78,7 +82,7 @@ var Pair = {}; Pair.setActive = function(pair, isActive) { if (isActive) { pair.isActive = true; - pair.timestamp = Common.now(); + pair.timeUpdated = Common.now(); } else { pair.isActive = false; pair.activeContacts = []; diff --git a/src/core/Engine.js b/src/core/Engine.js index d96879a..e17c0b2 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -34,11 +34,17 @@ var Engine = {}; positionIterations: 6, velocityIterations: 4, constraintIterations: 1, - pairs: {}, - pairsList: [], + pairs: { + table: {}, + list: [], + collisionStart: [], + collisionActive: [], + collisionEnd: [] + }, enableSleeping: false, timeScale: 1, input: {}, + events: [], timing: { fps: _fps, timestamp: 0, @@ -78,16 +84,6 @@ var Engine = {}; } }; - engine.events = { - tick: function(engine) { - Engine.update(engine, engine.timing.delta, engine.timing.correction); - }, - render: function(engine) { - if (engine.render.options.enabled) - engine.render.controller.world(engine); - } - }; - return engine; }; @@ -99,6 +95,7 @@ var Engine = {}; Engine.run = function(engine) { var timing = engine.timing, delta, + correction, counterTimestamp = 0, frameCounter = 0, deltaHistory = []; @@ -109,6 +106,13 @@ var Engine = {}; if (!engine.enabled) return; + // create an event object + var event = { + timestamp: timestamp + }; + + Events.trigger(engine, 'beforeTick', event); + delta = (timestamp - timing.timestamp) || _delta; // optimistically filter delta over a few frames, to improve stability @@ -120,20 +124,32 @@ var Engine = {}; delta = delta < engine.timing.deltaMin ? engine.timing.deltaMin : delta; delta = delta > engine.timing.deltaMax ? engine.timing.deltaMax : delta; + // verlet time correction + correction = delta / timing.delta; + + // update engine timing object timing.timestamp = timestamp; - timing.correction = delta / timing.delta; + timing.correction = correction; timing.delta = delta; + // fps counter frameCounter += 1; - if (timestamp - counterTimestamp >= 1000) { timing.fps = frameCounter * ((timestamp - counterTimestamp) / 1000); counterTimestamp = timestamp; frameCounter = 0; } - engine.events.tick(engine); - engine.events.render(engine); + Events.trigger(engine, 'tick beforeUpdate', event); + + Engine.update(engine, delta, correction); + + Events.trigger(engine, 'afterUpdate beforeRender', event); + + if (engine.render.options.enabled) + engine.render.controller.world(engine); + + Events.trigger(engine, 'afterTick afterRender', event); })(); }; @@ -151,16 +167,17 @@ var Engine = {}; broadphasePairs = [], i; - Body.resetForcesAll(world.bodies, world.gravity); Metrics.reset(engine.metrics); MouseConstraint.update(engine.mouseConstraint, world.bodies, engine.input); Body.updateAll(world.bodies, delta * engine.timeScale, correction, world.bounds); + // update all constraints for (i = 0; i < engine.constraintIterations; i++) { Constraint.updateAll(world.constraints); } + // broadphase pass: find potential collision pairs if (broadphase.controller) { broadphase.controller.update(broadphase.instance, world.bodies, engine); broadphasePairs = broadphase.instance.pairsList; @@ -168,25 +185,44 @@ var Engine = {}; broadphasePairs = world.bodies; } - var pairsUpdated = Manager.updatePairs(engine.pairs, engine.pairsList, broadphasePairs, engine.metrics, broadphase.detector), - pairsRemoved = Manager.removeOldPairs(engine.pairs, engine.pairsList); - - if (pairsUpdated || pairsRemoved) - engine.pairsList = Common.values(engine.pairs); + // narrowphase pass: find actual collisions, then create or update collision pairs + var collisions = broadphase.detector(broadphasePairs, engine.metrics); + + // update pairs + var pairs = engine.pairs; + Manager.updatePairs(pairs, collisions); + Manager.removeOldPairs(pairs); + + // trigger collision events + if (pairs.collisionStart.length > 0) { + Events.trigger(engine, 'collisionStart', { + pairs: pairs.collisionStart + }); + } + if (pairs.collisionActive.length > 0) { + Events.trigger(engine, 'collisionActive', { + pairs: pairs.collisionActive + }); + } + if (pairs.collisionEnd.length > 0) { + Events.trigger(engine, 'collisionEnd', { + pairs: pairs.collisionEnd + }); + } // wake up bodies involved in collisions if (engine.enableSleeping) - Sleeping.afterCollisions(engine.pairsList); + Sleeping.afterCollisions(pairs.list); // iteratively resolve velocity between collisions - Resolver.preSolveVelocity(engine.pairsList); + Resolver.preSolveVelocity(pairs.list); for (i = 0; i < engine.velocityIterations; i++) { - Resolver.solveVelocity(engine.pairsList); + Resolver.solveVelocity(pairs.list); } // iteratively resolve position between collisions for (i = 0; i < engine.positionIterations; i++) { - Resolver.solvePosition(engine.pairsList); + Resolver.solvePosition(pairs.list); } Resolver.postSolvePosition(world.bodies); @@ -195,6 +231,9 @@ var Engine = {}; Metrics.update(engine.metrics, engine); + // clear force buffers + Body.resetForcesAll(world.bodies, world.gravity); + return engine; }; @@ -238,8 +277,8 @@ var Engine = {}; Engine.clear = function(engine) { var world = engine.world; - engine.pairs = {}; - engine.pairsList = []; + engine.pairs.table = {}; + engine.pairs.list = []; World.addComposite(engine.world, engine.mouseConstraint); diff --git a/src/core/Metrics.js b/src/core/Metrics.js index 8f6afef..2ae0e92 100644 --- a/src/core/Metrics.js +++ b/src/core/Metrics.js @@ -59,7 +59,7 @@ var Metrics = {}; broadphase = engine.broadphase[engine.broadphase.current]; metrics.collisions = metrics.narrowDetections; - metrics.pairs = engine.pairsList.length; + metrics.pairs = engine.pairs.list.length; metrics.bodies = world.bodies.length; metrics.midEff = (metrics.narrowDetections / (metrics.midphaseTests || 1)).toFixed(2); metrics.narrowEff = (metrics.narrowDetections / (metrics.narrowphaseTests || 1)).toFixed(2); diff --git a/src/module/Outro.js b/src/module/Outro.js index 9f6e100..f855f3f 100644 --- a/src/module/Outro.js +++ b/src/module/Outro.js @@ -31,6 +31,7 @@ Matter.Vector = Vector; Matter.Vertices = Vertices; Matter.Gui = Gui; Matter.Render = Render; +Matter.Events = Events; // CommonJS module if (typeof exports !== 'undefined') { diff --git a/src/render/Render.js b/src/render/Render.js index 85111f1..63d09a9 100644 --- a/src/render/Render.js +++ b/src/render/Render.js @@ -82,8 +82,8 @@ var Render = {}; Render.constraint(world.constraints[i], context); if (options.showCollisions) - for (i = 0; i < engine.pairsList.length; i++) - Render.collision(engine, engine.pairsList[i], context); + for (i = 0; i < engine.pairs.list.length; i++) + Render.collision(engine, engine.pairs.list[i], context); if (options.showBroadphase && engine.broadphase.current === 'grid') Render.grid(engine, engine.broadphase[engine.broadphase.current].instance, context); @@ -118,7 +118,7 @@ var Render = {}; text += "\n"; text += "collisions: " + engine.metrics.collisions + space; - text += "pairs: " + engine.pairs.length + space; + text += "pairs: " + engine.pairs.list.length + space; text += "broad: " + engine.metrics.broadEff + space; text += "mid: " + engine.metrics.midEff + space; text += "narrow: " + engine.metrics.narrowEff + space;