From 0add2f00c9d9076df792112310e1519567307b02 Mon Sep 17 00:00:00 2001 From: liabru Date: Sat, 15 Mar 2014 17:28:51 +0000 Subject: [PATCH] implemented collision caching, big performance boost --- src/collision/Detector.js | 60 +++++++++++++++++++++++++++--------- src/collision/Grid.js | 19 ++---------- src/collision/SAT.js | 65 +++++++++++++++++++++++++++++---------- src/core/Engine.js | 4 +-- src/core/Metrics.js | 9 ++++-- src/render/Gui.js | 1 + 6 files changed, 106 insertions(+), 52 deletions(-) diff --git a/src/collision/Detector.js b/src/collision/Detector.js index 52027c4..b737992 100644 --- a/src/collision/Detector.js +++ b/src/collision/Detector.js @@ -13,16 +13,18 @@ var Detector = {}; /** * Description * @method collisions - * @param {pair[]} pairs - * @param {metrics} metrics + * @param {pair[]} broadphasePairs + * @param {engine} engine * @return {array} collisions */ - Detector.collisions = function(pairs, metrics) { - var collisions = []; + Detector.collisions = function(broadphasePairs, engine) { + var collisions = [], + metrics = engine.metrics, + pairsTable = engine.pairs.table; - for (var i = 0; i < pairs.length; i++) { - var bodyA = pairs[i][0], - bodyB = pairs[i][1]; + for (var i = 0; i < broadphasePairs.length; i++) { + var bodyA = broadphasePairs[i][0], + bodyB = broadphasePairs[i][1]; // NOTE: could share a function for the below, but may drop performance? @@ -36,12 +38,26 @@ var Detector = {}; // mid phase if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { - + + // find a previous collision we could reuse + var pairId = Pair.id(bodyA, bodyB), + pair = pairId in pairsTable ? pairsTable[pairId] : null, + previousCollision; + + if (pair && pair.isActive) { + previousCollision = pair.collision; + } else { + previousCollision = null; + } + // narrow phase - var collision = SAT.collides(bodyA, bodyB); + var collision = SAT.collides(bodyA, bodyB, previousCollision); metrics.narrowphaseTests += 1; + if (collision.reused) + metrics.narrowReuseCount += 1; + if (collision.collided) { collisions.push(collision); metrics.narrowDetections += 1; @@ -56,11 +72,13 @@ var Detector = {}; * Description * @method bruteForce * @param {body[]} bodies - * @param {metrics} metrics + * @param {engine} engine * @return {array} collisions */ - Detector.bruteForce = function(bodies, metrics) { - var collisions = []; + Detector.bruteForce = function(bodies, engine) { + var collisions = [], + metrics = engine.metrics, + pairsTable = engine.pairs.table; for (var i = 0; i < bodies.length; i++) { for (var j = i + 1; j < bodies.length; j++) { @@ -80,11 +98,25 @@ var Detector = {}; // mid phase if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) { + // find a previous collision we could reuse + var pairId = Pair.id(bodyA, bodyB), + pair = pairId in pairsTable ? pairsTable[pairId] : null, + previousCollision; + + if (pair && pair.isActive) { + previousCollision = pair.collision; + } else { + previousCollision = null; + } + // narrow phase - var collision = SAT.collides(bodyA, bodyB); - + var collision = SAT.collides(bodyA, bodyB, previousCollision); + metrics.narrowphaseTests += 1; + if (collision.reused) + metrics.narrowReuseCount += 1; + if (collision.collided) { collisions.push(collision); metrics.narrowDetections += 1; diff --git a/src/collision/Grid.js b/src/collision/Grid.js index 317d4ab..3e40a54 100644 --- a/src/collision/Grid.js +++ b/src/collision/Grid.js @@ -220,7 +220,7 @@ var Grid = {}; // keep track of the number of buckets the pair exists in // important for Grid.update to work - var pairId = _getPairId(body, bodyB); + var pairId = Pair.id(body, bodyB); if (grid.pairs[pairId]) { grid.pairs[pairId][2] += 1; } else { @@ -252,7 +252,7 @@ var Grid = {}; } else { // keep track of the number of buckets the pair exists in // important for Grid.update to work - var pairId = _getPairId(body, bodyB); + var pairId = Pair.id(body, bodyB); if (grid.pairs[pairId]) { var pairCount = grid.pairs[pairId][2]; grid.pairs[pairId][2] = pairCount > 0 ? pairCount - 1 : 0; @@ -261,21 +261,6 @@ var Grid = {}; } }; - /** - * Description - * @method _getPairId - * @private - * @param {} bodyA - * @param {} bodyB - */ - var _getPairId = function(bodyA, bodyB) { - if (bodyA.id < bodyB.id) { - return bodyA.id + ',' + bodyB.id; - } else { - return bodyB.id + ',' + bodyA.id; - } - }; - /** * Description * @method _createActivePairsList diff --git a/src/collision/SAT.js b/src/collision/SAT.js index e70fac8..a75e862 100644 --- a/src/collision/SAT.js +++ b/src/collision/SAT.js @@ -4,9 +4,7 @@ * @class SAT */ - // TODO: true circles and curves -// TODO: cache the previously found axis and body, and start there first for faster early out var SAT = {}; @@ -17,30 +15,60 @@ var SAT = {}; * @method collides * @param {body} bodyA * @param {body} bodyB + * @param {collision} previousCollision * @return {collision} collision */ - SAT.collides = function(bodyA, bodyB) { + SAT.collides = function(bodyA, bodyB, previousCollision) { var overlapAB, overlapBA, minOverlap, - collision = { collided: false, bodyA: bodyA, bodyB: bodyB}; + collision, + prevCol = previousCollision, + canReusePrevCol = false; - overlapAB = _overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); + if (prevCol) { + var motion = bodyA.speed * bodyA.speed + bodyA.angularSpeed * bodyA.angularSpeed; + motion += bodyB.speed * bodyB.speed + bodyB.angularSpeed * bodyB.angularSpeed; - if (overlapAB.overlap === 0) - return collision; + // can reuse if collision was resting + canReusePrevCol = prevCol && prevCol.collided && motion < 0.2; + } - overlapBA = _overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); + if (prevCol && canReusePrevCol) { - if (overlapBA.overlap === 0) - return collision; + // only need to test the previously found axis + var axes = [prevCol.bodyA.axes[prevCol.axisNumber]]; + minOverlap = _overlapAxes(prevCol.bodyA.vertices, prevCol.bodyB.vertices, axes); - if (overlapAB.overlap < overlapBA.overlap) { - minOverlap = overlapAB; + collision = previousCollision; + collision.reused = true; + + if (minOverlap.overlap <= 0) { + collision.collided = false; + return collision; + } } else { - minOverlap = overlapBA; - collision.bodyA = bodyB; - collision.bodyB = bodyA; + collision = { collided: false, bodyA: bodyA, bodyB: bodyB }; + + overlapAB = _overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes); + + if (overlapAB.overlap <= 0) + return collision; + + overlapBA = _overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes); + + if (overlapBA.overlap <= 0) + return collision; + + if (overlapAB.overlap < overlapBA.overlap) { + minOverlap = overlapAB; + } else { + minOverlap = overlapBA; + collision.bodyA = bodyB; + collision.bodyB = bodyA; + } + + collision.axisNumber = minOverlap.axisNumber; } collision.collided = true; @@ -111,12 +139,15 @@ var SAT = {}; ? projectionA.max - projectionB.min : projectionB.max - projectionA.min; - if (overlap <= 0) - return { overlap: 0 }; + if (overlap <= 0) { + result.overlap = overlap; + return result; + } if (overlap < result.overlap) { result.overlap = overlap; result.axis = axis; + result.axisNumber = i; } } diff --git a/src/core/Engine.js b/src/core/Engine.js index 7a0190e..41c13e9 100644 --- a/src/core/Engine.js +++ b/src/core/Engine.js @@ -262,9 +262,9 @@ var Engine = {}; } else { broadphasePairs = world.bodies; } - + // narrowphase pass: find actual collisions, then create or update collision pairs - var collisions = broadphase.detector(broadphasePairs, engine.metrics); + var collisions = broadphase.detector(broadphasePairs, engine); // update pairs var pairs = engine.pairs, diff --git a/src/core/Metrics.js b/src/core/Metrics.js index 2ae0e92..5e8c3e9 100644 --- a/src/core/Metrics.js +++ b/src/core/Metrics.js @@ -17,6 +17,8 @@ var Metrics = {}; return { narrowDetections: 0, narrowphaseTests: 0, + narrowReuse: 0, + narrowReuseCount: 0, midphaseTests: 0, broadphaseTests: 0, narrowEff: 0.0001, @@ -37,6 +39,8 @@ var Metrics = {}; Metrics.reset = function(metrics) { metrics.narrowDetections = 0; metrics.narrowphaseTests = 0; + metrics.narrowReuse = 0; + metrics.narrowReuseCount = 0; metrics.midphaseTests = 0; metrics.broadphaseTests = 0; metrics.narrowEff = 0; @@ -64,8 +68,9 @@ var Metrics = {}; metrics.midEff = (metrics.narrowDetections / (metrics.midphaseTests || 1)).toFixed(2); metrics.narrowEff = (metrics.narrowDetections / (metrics.narrowphaseTests || 1)).toFixed(2); metrics.broadEff = (1 - (metrics.broadphaseTests / (world.bodies.length || 1))).toFixed(2); - if (broadphase.instance) - metrics.buckets = Common.keys(broadphase.instance.buckets).length; + metrics.narrowReuse = (metrics.narrowReuseCount / (metrics.narrowphaseTests || 1)).toFixed(2); + //if (broadphase.instance) + // metrics.buckets = Common.keys(broadphase.instance.buckets).length; }; })(); \ No newline at end of file diff --git a/src/render/Gui.js b/src/render/Gui.js index 4beac60..7e62c05 100644 --- a/src/render/Gui.js +++ b/src/render/Gui.js @@ -94,6 +94,7 @@ var Gui = {}; metrics.add(engine.metrics, 'broadEff').listen(); metrics.add(engine.metrics, 'midEff').listen(); metrics.add(engine.metrics, 'narrowEff').listen(); + metrics.add(engine.metrics, 'narrowReuse').listen(); metrics.close(); var controls = datGui.addFolder('Add Body');