/** * See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js) * and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples. * * @class Grid */ var Grid = {}; (function() { /** * Description * @method create * @param {} options * @return {grid} A new grid */ Grid.create = function(options) { var defaults = { controller: Grid, detector: Detector.collisions, buckets: {}, pairs: {}, pairsList: [], bucketWidth: 48, bucketHeight: 48 }; return Common.extend(defaults, options); }; /** * Description * @method update * @param {grid} grid * @param {body[]} bodies * @param {engine} engine * @param {boolean} forceUpdate */ Grid.update = function(grid, bodies, engine, forceUpdate) { var i, col, row, world = engine.world, buckets = grid.buckets, bucket, bucketId, gridChanged = false; // @if DEBUG var metrics = engine.metrics; metrics.broadphaseTests = 0; // @endif for (i = 0; i < bodies.length; i++) { var body = bodies[i]; if (body.isSleeping && !forceUpdate) continue; // don't update out of world bodies if (body.bounds.max.x < 0 || body.bounds.min.x > world.bounds.width || body.bounds.max.y < 0 || body.bounds.min.y > world.bounds.height) continue; var newRegion = _getRegion(grid, body); // if the body has changed grid region if (!body.region || newRegion.id !== body.region.id || forceUpdate) { // @if DEBUG metrics.broadphaseTests += 1; // @endif if (!body.region || forceUpdate) body.region = newRegion; var union = _regionUnion(newRegion, body.region); // 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 = _getBucketId(col, row); bucket = buckets[bucketId]; 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) _bucketRemoveBody(grid, bucket, body); } } // add to new region buckets if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) { if (!bucket) bucket = _createBucket(buckets, bucketId); _bucketAddBody(grid, bucket, body); } } } // 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 = _createActivePairsList(grid); }; /** * Description * @method clear * @param {grid} grid */ Grid.clear = function(grid) { grid.buckets = {}; grid.pairs = {}; grid.pairsList = []; }; /** * Description * @method _regionUnion * @private * @param {} regionA * @param {} regionB * @return CallExpression */ var _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); return _createRegion(startCol, endCol, startRow, endRow); }; /** * Description * @method _getRegion * @private * @param {} grid * @param {} body * @return CallExpression */ var _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 _createRegion(startCol, endCol, startRow, endRow); }; /** * Description * @method _createRegion * @private * @param {} startCol * @param {} endCol * @param {} startRow * @param {} endRow * @return ObjectExpression */ var _createRegion = function(startCol, endCol, startRow, endRow) { return { id: startCol + ',' + endCol + ',' + startRow + ',' + endRow, startCol: startCol, endCol: endCol, startRow: startRow, endRow: endRow }; }; /** * Description * @method _getBucketId * @private * @param {} column * @param {} row * @return BinaryExpression */ var _getBucketId = function(column, row) { return column + ',' + row; }; /** * Description * @method _createBucket * @private * @param {} buckets * @param {} bucketId * @return bucket */ var _createBucket = function(buckets, bucketId) { var bucket = buckets[bucketId] = []; return bucket; }; /** * Description * @method _bucketAddBody * @private * @param {} grid * @param {} bucket * @param {} body */ var _bucketAddBody = function(grid, bucket, body) { // add new pairs for (var i = 0; i < bucket.length; i++) { var bodyB = bucket[i]; if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic)) continue; // 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; } else { grid.pairs[pairId] = [body, bodyB, 1]; } } // add to bodies (after pairs, otherwise pairs with self) bucket.push(body); }; /** * Description * @method _bucketRemoveBody * @private * @param {} grid * @param {} bucket * @param {} body */ var _bucketRemoveBody = function(grid, bucket, body) { // remove from bucket bucket.splice(Common.indexOf(bucket, body), 1); // update pair counts 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; } }; /** * Description * @method _createActivePairsList * @private * @param {} grid * @return pairs */ var _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); } else { delete grid.pairs[pairKeys[k]]; } } return pairs; }; })();