Methods now accept Rect-like objects.
authordsc <david.schoonover@gmail.com>
Mon, 20 Dec 2010 05:47:50 +0000 (21:47 -0800)
committerdsc <david.schoonover@gmail.com>
Mon, 20 Dec 2010 05:47:50 +0000 (21:47 -0800)
src/tanks/map/pathmap.cjs

index b1957a9..ac0fb83 100644 (file)
@@ -14,7 +14,13 @@ var Y        = require('Y').Y
 ,
 
 
-
+/**
+ * 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', {
@@ -36,6 +42,10 @@ 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
@@ -51,12 +61,6 @@ QuadTree.subclass('PathMap', {
     },
     
     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);
@@ -80,72 +84,75 @@ QuadTree.subclass('PathMap', {
     
     
     
-    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
@@ -168,6 +175,7 @@ QuadTree.subclass('PathMap', {
                 }
             }
             
+            // cache result
             this._grid = this.reduce(function(grid, v, r){
                 
                 // Ignore bullets and boundary-walls in pathing
@@ -195,19 +203,7 @@ QuadTree.subclass('PathMap', {
         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)
@@ -218,7 +214,7 @@ QuadTree.subclass('PathMap', {
      * @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()
         
@@ -231,20 +227,52 @@ QuadTree.subclass('PathMap', {
         ,   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);