,
-
+/**
+ * A QuadTree which aids in pathing for AI.
+ * - QuadTree methods which took rect coords (x1,y1, x2,y2) will accept
+ * anything Rect-like (having properties x1,y1, x2,y2).
+ * - Pathing methods will account for the unit's size when calculating paths,
+ * intersections, blocking, and similar tasks.
+ */
PathMap =
exports['PathMap'] =
QuadTree.subclass('PathMap', {
this.game.addEventListener('ready', this.setup.bind(this, x1,y1, x2,y2));
},
+ /**
+ * Adds boundary walls and notifies the level.
+ * TODO: I hate this. The level shouldn't have shit to do with pathing.
+ */
setup : function setup(x1,y1, x2,y2){
var w = this.width, h = this.height
, level = this.level
},
wallObstructs : function wallObstructs(line){
- // var walls = this.walls;
- // for (var i=0, L=walls.length, w=walls[i]; i<L; w=walls[++i]) {
- // if ( w.boundingBox.intersects(line) )
- // return w;
- // }
- // return false;
return this.walls.some(function(wall){
return wall.boundingBox.intersects(line);
}, this);
- moveBlocked : function moveBlocked(agent, trj, to, bb){
- bb = bb || agent.boundingBox;
- var blockers, blocker, msg
- , side = null
- , tobb = bb.relocated(to.x,to.y)
- , x1 = tobb.x1, y1 = tobb.y1
- , x2 = tobb.x2, y2 = tobb.y2
-
- , ro = bb.relOrigin
- , bw = bb.width, bh = bb.height
- , offX = bw - ro.x, offY = bh = ro.y
-
- // , x1 = to.x+offX, y1 = to.y+offY
- // , x2 = x1+bw, y2 = y1+bh
- ;
-
- blockers = this.get(x1,y1, x2,y2).remove(agent).end();
- blocker = blockers[0];
-
- // Not blocked
- if (!blocker) return false;
-
- // Literal corner case :P
- // XXX: needs better detection of corner
- if (blockers.length > 1) {
- msg = 'corner of '+blockers.slice(0);
- to = trj.p1;
-
- // Normal reflection against one line
- } else {
- msg = blocker+' on the ';
-
- var B = blocker.boundingBox;
- if (bb.x2 <= B.x1 && x2 >= B.x1) {
- msg += 'left';
- side = B.leftSide;
- to = trj.pointAtX(B.x1-offX-1);
-
- } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
- msg += 'right';
- side = B.rightSide;
- to = trj.pointAtX(B.x2+ro.x+1);
-
- } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
- msg += 'top';
- side = B.topSide;
- to = trj.pointAtY(B.y1-offY-1);
-
- } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
- msg += 'bottom';
- side = B.bottomSide;
- to = trj.pointAtY(B.y2+ro.y+1);
- }
-
- msg += ' side';
- }
-
- return { 'msg':msg, 'blockers':blockers, 'side':side, 'to':to };
- },
-
+ // moveBlocked : function moveBlocked(agent, trj, to, bb){
+ // bb = bb || agent.boundingBox;
+ // var blockers, blocker, msg
+ // , side = null
+ // , tobb = bb.relocated(to.x,to.y)
+ // , x1 = tobb.x1, y1 = tobb.y1
+ // , x2 = tobb.x2, y2 = tobb.y2
+ //
+ // , ro = bb.relOrigin
+ // , bw = bb.width, bh = bb.height
+ // , offX = bw - ro.x, offY = bh = ro.y
+ //
+ // // , x1 = to.x+offX, y1 = to.y+offY
+ // // , x2 = x1+bw, y2 = y1+bh
+ // ;
+ //
+ // blockers = this.get(x1,y1, x2,y2).remove(agent).end();
+ // blocker = blockers[0];
+ //
+ // // Not blocked
+ // if (!blocker) return false;
+ //
+ // // Literal corner case :P
+ // // XXX: needs better detection of corner
+ // if (blockers.length > 1) {
+ // msg = 'corner of '+blockers.slice(0);
+ // to = trj.p1;
+ //
+ // // Normal reflection against one line
+ // } else {
+ // msg = blocker+' on the ';
+ //
+ // var B = blocker.boundingBox;
+ // if (bb.x2 <= B.x1 && x2 >= B.x1) {
+ // msg += 'left';
+ // side = B.leftSide;
+ // to = trj.pointAtX(B.x1-offX-1);
+ //
+ // } else if (bb.x1 >= B.x2 && x1 <= B.x2) {
+ // msg += 'right';
+ // side = B.rightSide;
+ // to = trj.pointAtX(B.x2+ro.x+1);
+ //
+ // } else if (bb.y2 <= B.y1 && y2 >= B.y1) {
+ // msg += 'top';
+ // side = B.topSide;
+ // to = trj.pointAtY(B.y1-offY-1);
+ //
+ // } else if (bb.y1 >= B.y2 && y1 <= B.y2) {
+ // msg += 'bottom';
+ // side = B.bottomSide;
+ // to = trj.pointAtY(B.y2+ro.y+1);
+ // }
+ //
+ // msg += ' side';
+ // }
+ //
+ // return { 'msg':msg, 'blockers':blockers, 'side':side, 'to':to };
+ // },
+ // TODO: Fold A* into this class.
+ // TODO: Deal with bbox
/**
* @protected Creates a pathing grid suitable for A* search.
- * TODO: Fold A* into this class.
+ * @param {Thing|BoundingBox} bbox Denotes the bounds of object under pathing:
+ * - An object with a `boundingBox` property
+ * - A BoundingBox-like object (has properties relOrigin, width, height)
*/
- grid : function grid(){
+ grid : function grid(bbox){
if ( !this._grid ) {
var size = this.gridSquare
, floor = Math.floor, ceil = Math.ceil
}
}
+ // cache result
this._grid = this.reduce(function(grid, v, r){
// Ignore bullets and boundary-walls in pathing
return this._grid;
},
- vec2Square : function vec2Square(x,y){
- if (x instanceof Array){ y = x.y; x = x.x; }
- var floor = Math.floor, size = this.gridSquare;
- return new Vec(floor(x/size), floor(y/size));
- },
-
- square2Vec : function square2Vec(x,y){
- if (x instanceof Array){ y = x.y; x = x.x; }
- var floor = Math.floor, size = this.gridSquare;
- return new Vec(floor(x)*size, floor(y)*size);
- },
-
- path : function path(start, end, agent){
+ path : function path(agent, start, end){
return this.gridPath(start, end, agent)
.invoke('scale', this.gridSquare)
.invoke('add', this.gridSquareMid)
* @protected
* Generates grid-sized path from start to end.
*/
- gridPath : function gridPath(start, end, agent){
+ _gridPath : function gridPath(agent, start, end){
var size = this.gridSquare, floor = Math.floor
, grid = this.grid()
, endN = grid[endX][endY]
;
return Y(astar.search(grid, startN, endN))
- }
+ },
+
+ vec2Square : function vec2Square(x,y){
+ if (x instanceof Array){ y = x.y; x = x.x; }
+ var floor = Math.floor, size = this.gridSquare;
+ return new Vec(floor(x/size), floor(y/size));
+ },
+
+ square2Vec : function square2Vec(x,y){
+ if (x instanceof Array){ y = x.y; x = x.x; }
+ var floor = Math.floor, size = this.gridSquare;
+ return new Vec(floor(x)*size, floor(y)*size);
+ },
+
+
});
+// Wrap QuadTree Methods to accept Rect-like objects
+'overlaps get set removeAll leaves collect'
+ .split(' ')
+ .forEach(function(name){
+ var method = PathMap.fn[name] || QuadTree.fn[name];
+ PathMap.fn[name] = function(o){
+ var args = arguments, xy;
+ if ('x1' in o){
+ xy = [o.x1,o.y1, o.x2,o.y2];
+ args = (args.length === 1) ? xy : xy.concat(Y(args,1));
+ }
+ return method.apply(this, args);
+ };
+ });
+// Invalidate grid cache
+// TODO: Goes away when I fix A*
'set remove removeAll clear'
.split(' ')
.forEach(function(name){
+ var method = PathMap.fn[name] || QuadTree.fn[name];
PathMap.fn[name] = function(){
delete this._grid;
- return QuadTree.fn[name].apply(this, arguments);
+ return method.apply(this, arguments);
};
});
+// Stay sync'd with config
config.updateOnChange(
['pathing.gridSquare', 'pathing.gridSquareMid'],
PathMap.fn);