File: src\sim\Common\Grid.js
'use strict';
var Vec2 = require('./Vec2');
/**
* Grid hashmap to store entities indexed by their position.
*
* @class Grid
* @module CrowdSim
* @submodule Common
* @constructor
* @param {Number} near is the cell size for the hashmap. Also the maximum distance to be consider "neighbours"
*/
var Grid = function(near) {
this.near = near;
this.grid = {};
};
/**
* Insert entities in the hashmap.
*
* @method insert
* @param {Array} entities
*/
Grid.prototype.insert = function(entities) {
for (var i in entities) {
var entity = entities[i];
var key = this._key(entity);
if (this.grid.hasOwnProperty(key)) {
this.grid[key].push(entity);
} else {
this.grid[key] = [entity];
}
}
};
/**
* Insert one entity.
*
* @method insertOne
* @param {Entity} entity
* @param {Number} x coordinate, if null entity.pos[0] is used
* @param {Number} y coordinate, if null entity.pos[1] is used
*/
Grid.prototype.insertOne = function(entity, x, y) {
var key = this._key(entity, x, y);
if (this.grid.hasOwnProperty(key)) {
this.grid[key].push(entity);
} else {
this.grid[key] = [entity];
}
};
/**
* Helper to update multiple contexts area points by sampling their area with the cell size.
*
* @method updateContextsHelper
* @param {Array} contexts
*/
Grid.prototype.updateContextsHelper = function(contexts) {
this.grid = {};
for (var i in contexts) {
var context = contexts[i];
var init = context.getMinXY();
var end = context.getMaxXY();
// generate samples from context to insert into grid
for (var x = init[0]; x <= end[0]; x += this.near) {
for (var y = init[1]; y <= end[1]; y += this.near) {
this.insertOne(context, x, y);
}
}
}
};
/**
* Helper to update multiple walls area points by sampling their path with the cell size.
*
* @method updateWallsHelper
* @param {Array} walls
*/
Grid.prototype.updateWallsHelper = function(walls) {
this.grid = {};
for (var w in walls) {
var wall = walls[w];
var joints = wall.getJoints();
if (joints.length > 0) {
var point = Vec2.create();
var squareStep = 2 * this.near * this.near; // sample step to ensure that line cross region at least half square
for (var j = 1; j < joints.length; j++) {
var squaredDistance = Vec2.squaredDistance(joints[j - 1].pos, joints[j].pos);
var samples = Math.floor(squaredDistance / squareStep);
for (var s = 0; s < samples; s++) {
// generate sample of segment
Vec2.lerp(point, joints[j - 1].pos, joints[j].pos, s / samples);
this.insertOne(wall, point[0], point[1]);
}
}
}
}
};
/**
* Clear the hashamp and insert entities.
*
* @method updateAll
* @param {Array} entities
*/
Grid.prototype.updateAll = function(entities) {
this.clear();
this.insert(entities);
};
/**
* Update given entities mapping.
*
* @method update
* @param {Array} entities
*/
Grid.prototype.update = function(entities) {
this.remove(entities);
this.insert(entities);
};
/**
* Clear the hashmap.
*
* @method clear
*/
Grid.prototype.clear = function() {
this.grid = {};
};
/**
* Remove the given entities.
*
* @method remove
* @param {Array} entities
*/
Grid.prototype.remove = function(entities) {
for (var i in entity) {
var entity = entities[i];
var key = this._key(entity);
var bucket = this.grid[key];
var j = bucket.indexOf(entity);
this.grid[key].splice(j, 1);
}
};
/**
* Gets neighbours to (x,y) point or Entity.
* @method neighbours
* @param {Entity} entity
* @param {Number} x coordinate, if null entity.pos[0] is used
* @param {Number} y coordinate, if null entity.pos[1] is used
* @return {LazySequence} neighbours
*/
Grid.prototype.neighbours = function(entity, x, y) {
var that = this;
var o = this.near / 2;
x = x || entity.pos[0];
y = y || entity.pos[1];
var keys = this._keyNeighbours(x, y);
// find neighbours in hashmap looking in all buckets and filtering empty or duplicates
return Lazy(keys).map(function(key) {
return that.grid[key];
}).flatten().filter(function(e) { return e;});
};
/**
* Gets neighbours to a context by sampling its position with the cell size.
*
* @method neighboursContext
* @param {Context} context
* @return {LazySequence} neighbours
*/
Grid.prototype.neighboursContext = function(context) {
// generate sampling and find entities near
var init = context.getMinXY();
var end = context.getMaxXY();
var neighbours = Lazy([]);
for (var x = init[0]; x < end[0] + this.near; x += this.near) {
for (var y = init[1]; y < end[1] + this.near; y += this.near) {
neighbours = neighbours.concat(this.neighbours(null, x, y));
}
}
return neighbours.flatten();
};
/**
* Builds the keys of the neighbours of the position (x,y).
*
* @method _keyNeighbours
* @param {Number} x coordinate
* @param {Number} y coordinate
* @return {Array} neighbours keys
*/
Grid.prototype._keyNeighbours = function(x, y) {
x = Math.floor(x / this.near);
y = Math.floor(y / this.near);
return [
(x - 1) + ':' + (y + 1), x + ':' + (y + 1), (x + 1) + ':' + (y + 1),
(x - 1) + ':' + y , x + ':' + y , (x + 1) + ':' + y,
(x - 1) + ':' + (y - 1), x + ':' + (y - 1), (x + 1) + ':' + (y - 1)
];
};
/**
* Build the key to map coordinates to the hashmap.
*
* @method _key
* @param {Entity} entity
* @param {Number} x coordinate, if null entity.pos[0] is used
* @param {Number} y coordinate, if null entity.pos[1] is used
* @return {String} key
*/
Grid.prototype._key = function(entity, x, y) {
// use x,y if available if not just entity position
x = x || entity.pos[0];
x = Math.floor(x / this.near);
y = y || entity.pos[1];
y = Math.floor(y / this.near);
return x + ':' + y;
};
module.exports = Grid;