Source: tilemap/TilemapLayer.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/** 
 * Creates a new object of TilemapLayer.
 * 
 * @constructor
 *
 * @param {rune.tilemap.Tilemap} map Reference to the map to which the layer belongs.
 * @param {Array.<number>} [data] Tilemap data.
 * 
 * @class
 * @classdesc
 * 
 * The rune.tilemap.TilemapLayer class represents a layer (buffer) within a 
 * Tilemap. Each layer has its own set of Tiles, which can be managed from the 
 * layer object. A Tilemap object automatically instantiates two layers; one 
 * for the back buffer, and one for the front buffer.
 *
 * @see rune.tilemap.Tilemap
 */
rune.tilemap.TilemapLayer = function(map, data) {
    
    //--------------------------------------------------------------------------
    // Public properties
    //--------------------------------------------------------------------------
    
    /**
     * Whether the layer should be rendered or not. Useful for troubleshooting.
     *
     * @type {boolean}
     * @private
     */
    this.visible = true;
    
    //--------------------------------------------------------------------------
    // Private properties
    //--------------------------------------------------------------------------
    
    /**
     * Map data.
     *
     * @type {Array.<number>}
     * @private
     */
    this.m_data = data || null;
    
    /**
     * Reference to the map to which the layer belongs.
     *
     * @type {rune.tilemap.Tilemap}
     * @private
     */
    this.m_map = map;
    
    /**
     * Contains Path objects to be rendered on top of the layer. Used 
     * primarily for troubleshooting.
     *
     * @type {rune.util.Paths}
     * @private
     */
    this.m_paths = new rune.util.Paths();
    
    /**
     * Reference to the Tile object used to represent all the tiles in the 
     * layer. This reference is reused for each tile in order to reduce 
     * and stabilize memory consumption.
     *
     * @type {rune.tilemap.Tile}
     * @private
     */
    this.m_tmpTile = new rune.tilemap.Tile(this.m_map['tileWidth'], this.m_map['tileHeight']);
    
    //--------------------------------------------------------------------------
    // Constructor call
    //--------------------------------------------------------------------------
    
    /**
     * Invokes secondary class constructor.
     */
    this.m_construct();
};

//------------------------------------------------------------------------------
// Public getter and setter methods
//------------------------------------------------------------------------------

/**
 * Reference to the raw map data of the layer, i.e., an array containing all 
 * map indices. Note that this reference is intended for read-only purposes; 
 * avoid modifying this list directly.
 *
 * @member {Array.<number>} data
 * @memberof rune.tilemap.TilemapLayer
 * @instance
 * @readonly
 */
Object.defineProperty(rune.tilemap.TilemapLayer.prototype, "data", {
    /**
     * @this rune.tilemap.TilemapLayer
     * @ignore
     */
    get : function() {
        return this.m_data;
    }
});

/**
 * A stack of Path objects. Each object included in the stack is rendered on 
 * top of the layer. Used primarily for troubleshooting.
 *
 * @member {rune.util.Paths} paths
 * @memberof rune.tilemap.TilemapLayer
 * @instance
 * @readonly
 */
Object.defineProperty(rune.tilemap.TilemapLayer.prototype, "paths", {
    /**
     * @this rune.tilemap.TilemapLayer
     * @ignore
     */
    get : function() {
        return this.m_paths;
    }
});

//------------------------------------------------------------------------------
// Public prototype methods (API)
//------------------------------------------------------------------------------

/**
 * Clears all map data.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.clear = function() {
    this.m_data = Array(this.m_map['numTiles']);
    for (var i = 0; i < this.m_data.length; i++) {
        this.m_data[i] = 0;
    }
};

/**
 * Returns a Path object that describes the shortest path between two points 
 * within the layer data.
 *
 * @param {number} sx The x-coordinate of the starting point.
 * @param {number} sy The y-coordinate of the starting point.
 * @param {number} gx The x-coordinate of the target point.
 * @param {number} gy The y-coordinate of the target point.
 * @param {boolean} [md=false] Whether the Path object is allowed to include diagonal paths.
 *
 * @returns {rune.util.Path}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.getPath = function(sx, sy, gx, gy, md) {
    var si = this.getTileIndexOf(sx, sy);
    var gi = this.getTileIndexOf(gx, gy);
    
    var st = this.getTileAt(si);
    var gt = this.getTileAt(gi);
    
    if ((st['allowCollisions'] > 0) && (gt['allowCollisions'] > 0)) return null;
    
    var ad = this.m_computeDistance(si, gi, md);
    if (ad == null) return null;
    
    var ap = [];
    this.m_walk(ad, gi, ap, md);
    
    var np = new rune.util.Path();
    var pi = ap.length - 1;
    var cn = null;
    
    while (pi >= 0) {
        cn = ap[pi--];
        if (cn != null) {
            np.addPoint(cn, true);
        } 
    }
    
    return np;
};

/**
 * Returns a path object that describes the shortest path between two points in the layer data, where the start and 
 * destination points are defined via Point objects.
 *
 * @param {rune.geom.Point} s Starting point.
 * @param {rune.geom.Point} g Target point.
 * @param {boolean} [md=false] Whether the Path object is allowed to include diagonal paths.
 *
 * @returns {rune.util.Path}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.getPathBetweenPoints = function(s, g, md) {
    return this.getPath(
        s['x'],
        s['y'],
        g['x'],
        g['y'],
        md
    );
};

/**
 * Returns a Tile object based on a specified tile index.
 *
 * @param {number} i Tile index.
 *
 * @returns {rune.tilemap.Tile}
 */
rune.tilemap.TilemapLayer.prototype.getTileAt = function(i) {
    var tw = this.m_map['tileWidth'];
    var th = this.m_map['tileHeight'];
    var wt = this.m_map['widthInTiles'];
    var tt = this.m_tmpTile;
    var tx = i % wt;
    var ty = Math.floor(i / wt);
    var tp = this.m_map.getTilePropertiesOf(this.getTileValueAt(i));
        
    tt.set(
        i,
        tx * tw,
        ty * th,
        tp
    );
    
    return tt;
};

/**
 * Returns a Tile object based on the given x and y coordinates.
 *
 * @param {number} x X coordinate.
 * @param {number} y Y coordinate.
 *
 * @returns {rune.tilemap.Tile}
 */
rune.tilemap.TilemapLayer.prototype.getTileOf = function(x, y) {
    return this.getTileAt(
        this.getTileIndexOf(x, y)
    );
};

/**
 * Returns a Tile object based on the specified Point object.
 *
 * @param {rune.geom.Point} p Point object.
 *
 * @returns {rune.tilemap.Tile}
 */
rune.tilemap.TilemapLayer.prototype.getTileOfPoint = function(p) {
    return this.getTileOf(
        p['x'],
        p['y']
    );
};

/**
 * Returns a list of Tile indices that fit within a specified rectangular area.
 *
 * @param {number} x The x-coordinate of the clipping area.
 * @param {number} y The y-coordinate of the clipping area.
 * @param {number} w The width of the clipping area.
 * @param {number} h The height of the clipping area.
 *
 * @returns {Array.<number>}
 */
rune.tilemap.TilemapLayer.prototype.getTileIndexesIn = function(x, y, w, h) {
    var od = [];
    var tw = this.m_map['tileWidth'];
    var th = this.m_map['tileHeight'];
    var wt = this.m_map['widthInTiles'];
    
    var sx = Math.floor(x / tw);
    var sy = Math.floor(y / th);
    var si = sy * wt + sx;
    
    var bx = (x % tw) ? 1 : 0;
    var by = (y % th) ? 1 : 0;
    
    var iw = Math.ceil(w / tw) + bx;
    var ih = Math.ceil(h / th) + by;
    var ti = 0;
    
    var ii = 0;
    for (var iy = 0; iy < ih; iy++) {
        for (var ix = 0; ix < iw; ix++) {
            od[ti] = si + ix + (iy * wt);
            ti++;
        }
    }
    
    return od;
};

/**
 * Returns a list of Tile indices contained within a described Rectangle object.
 *
 * @param {rune.geom.Rectangle} r Rectangle object.
 *
 * @returns {Array.<number>}
 */
rune.tilemap.TilemapLayer.prototype.getTileIndexesInRect = function(r) {
    return this.getTileIndexesIn(
        r['x'],
        r['y'],
        r['width'],
        r['height']
    );
};

/**
 * Returns the tile index of a specific position.
 *
 * @param {number} x The x-coordinate of the position.
 * @param {number} y The y-coordinate of the position.
 *
 * @returns {number}
 */
rune.tilemap.TilemapLayer.prototype.getTileIndexOf = function(x, y) {    
    var tw = this.m_map['tileWidth'];
    var th = this.m_map['tileHeight'];
    var wt = this.m_map['widthInTiles'];
        
    var sx = Math.floor(x / tw);
    var sy = Math.floor(y / th);
    var si = sy * wt + sx;
    
    return si;
};

/**
 * Returns the tile index of a specific position, via a Point object.
 *
 * @param {rune.geom.Point} p Point object.
 *
 * @returns {number}
 */
rune.tilemap.TilemapLayer.prototype.getTileIndexOfPoint = function(p) {
    return this.getTileIndexOf(
        p['x'],
        p['y']
    );
};

/**
 * Returns the tile value of a specific tile index.
 *
 * @param {number} i Tile index.
 *
 * @returns {number}
 */
rune.tilemap.TilemapLayer.prototype.getTileValueAt = function(i) {
    return this.m_data[i];
};

/**
 * Returns tile values that fit within a specific rectangle (clipping area).
 *
 * @param {number} x The x-coordinate of the clipping area.
 * @param {number} y The y-coordinate of the clipping area.
 * @param {number} w The width of the clipping area.
 * @param {number} h The height of the clipping area.
 *
 * @returns {Array.<number>}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.getTileValuesIn = function(x, y, w, h) {
    var od = [];
    var td = this.m_data;
    var ti = this.getTileIndexesIn(x, y, w, h);
    
    for (var i = 0; i < ti.length; i++) {
        od.push(td[ti[i]]);
    }
    
    return od;
};

/**
 * Returns tile values that fit within a specific rectangle object (clipping area).
 *
 * @param {rune.geom.Rectangle} r Rectangle object.
 *
 * @returns {Array.<number>}
 */
rune.tilemap.TilemapLayer.prototype.getTileValuesInRect = function(r) {
    return this.getTileValuesIn(
        r['x'],
        r['y'],
        r['width'],
        r['height']
    );
};

/**
 * Returns the tile value of a specific position.
 *
 * @param {number} x The x-coordinate of the position.
 * @param {number} y The y-coordinate of the position.
 *
 * @returns {number}
 */
rune.tilemap.TilemapLayer.prototype.getTileValueOf = function(x, y) {
    var td = this.m_data;
    var ti = this.getTileIndexOf(x, y);
    
    return td[ti];
};

/**
 * Return the tile value of a specific position, which is defined via a 
 * Point object.
 *
 * @param {rune.geom.Point} p Point object.
 *
 * @returns {number}
 */
rune.tilemap.TilemapLayer.prototype.getTileValueOfPoint = function(p) {
    return this.getTileValueOf(
        p['x'],
        p['y']
    );
};

/**
 * Evaluates whether the tilemap layer overlaps or intersects another object. 
 * This process only includes tiles that are solid ie. which has some form 
 * of collision data associated with it.
 *
 * @param {rune.display.DisplayObjectContainer|rune.display.InteractiveObject|rune.display.DisplayGroup|rune.geom.Point|Array} obj The object to be evaluated.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTest = function(obj, callback, scope) {
    if      (obj instanceof rune.display.Stage)             return this.hitTestChildrenOf(obj, callback, scope);
    else if (obj instanceof rune.display.InteractiveObject) return this.hitTestObject(obj, callback, scope);
    else if (obj instanceof rune.display.DisplayGroup)      return this.hitTestGroup(obj, callback, scope);
    else if (obj instanceof rune.geom.Point)                return this.hitTestPoint(obj, callback, scope);
    else if (obj instanceof Array)                          return this.hitTestContentOf(obj, callback, scope);
    else                                                    return false;
};

/**
 * Evaluates whether the tilemap layer overlaps or intersects the hitbox 
 * of any of the child objects within the specified parent object.
 *
 * @param {rune.display.DisplayObjectContainer} parent The object to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestChildrenOf = function(parent, callback, scope) {
    var result = false;
    parent.forEachChild(function(child) {
        if (this.hitTestObject(child, callback, scope)) {
            result = true;
        }
    }, this);
    
    return result;
};

/**
 * Evaluates whether the tilemap layer overlaps or intersects the hitbox 
 * of any of the contents of an array.
 *
 * @param {Array} array The array to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestContentOf = function(array, callback, scope) {
    var result = false;
    for (var i = 0; i < array.length; i++) {
        if (this.hitTestObject(array[i], callback, scope)) {
            result = true;
        }
    }
    
    return result;
};

/**
 * Evaluates whether the tilemap layer overlaps or intersects the hitbox of 
 * another interactive object.
 *
 * @param {rune.display.InteractiveObject} obj The object to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestObject = function(obj, callback, scope) {
    var result = false;
    var tile = null;
    var tiles = this.getTileIndexesInRect(obj['hitbox']);
    for (var i = 0; i < tiles.length; i++) {
        var value = this.getTileValueAt(tiles[i]);
        if (value > 0) {
            tile = this.getTileAt(tiles[i]);
            if (obj.hitTestObject(tile, callback, scope)) {
                result = true;
            }
        }
    }
    
    return result;
};

/**
 * Evaluates whether the tilemap layer overlaps or intersects the hitbox of 
 * any of the members within the specified group.
 *
 * @param {rune.display.DisplayGroup} group The group to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestGroup = function(group, callback, scope) {
    var result = false;
    group.forEachMember(function(member) {
        if (this.hitTestObject(member, callback, scope)) {
            result = true;
        }
    }, this);
    
    return result;
};

/**
 * Evaluates whether the specified Point object overlaps a solid tile in 
 * the tilemap layer.
 *
 * @param {rune.geom.Point} point The point to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestPoint = function(point, callback, scope) {
    var i = this.getTileIndexOfPoint(point);
    var t = this.getTileAt(i);
    
    return t.hitTestPoint(point, callback, scope);
};

/**
 * Evaluates and resolves collision between the tilemap layer and an interactive object's hitbox.
 *
 * @param {rune.display.Stage|rune.display.InteractiveObject|rune.display.DisplayGroup|Array} obj The object to be evaluated.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestAndSeparate = function(obj, callback, scope) {
    if      (obj instanceof rune.display.Stage)             return this.hitTestAndSeparateChildrenOf(obj, callback, scope);
    else if (obj instanceof rune.display.InteractiveObject) return this.hitTestAndSeparateObject(obj, callback, scope);
    else if (obj instanceof rune.display.DisplayGroup)      return this.hitTestAndSeparateGroup(obj, callback, scope);
    else if (obj instanceof Array)                          return this.hitTestAndSeparateContentOf(obj, callback, scope);
    else                                                    return false;
};

/**
 * Evaluates and resolves collision between the tilemap layer and the hitbox of child objects of the specified parent object.
 *
 * @param {rune.display.DisplayObjectContainer} parent The parent object to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestAndSeparateChildrenOf = function(parent, callback, scope) {
    var result = false;
    parent.forEachChild(function(child) {
        if (this.hitTestAndSeparateObject(child, callback, scope)) {
            result = true;
        }
    }, this);
    
    return result;
};

/**
 * Calculates and resolves collision between a Tilemap layer and 
 * objects in an array.
 *
 * @param {Array} array The array object to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestAndSeparateContentOf = function(array, callback, scope) {
    var result = false;
    for (var i = 0; i < array.length; i++) {
        if (this.hitTestAndSeparate(array[i], callback, scope)) {
            result = true;
        }
    }
    
    return result;
};

/**
 * Evaluates and resolves collision between the tilemap layer and the hitbox of an interactive object.
 *
 * @param {rune.display.InteractiveObject} obj The obje5
 ct to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestAndSeparateObject = function(obj, callback, scope) {
    var result = false;
    var tile = null;
    var tiles = this.getTileIndexesInRect(obj['hitbox']);
    for (var i = 0; i < tiles.length; i++) {
        var value = this.getTileValueAt(tiles[i]);
        if (value > 0) {
            tile = this.getTileAt(tiles[i]);
            if (obj.hitTestAndSeparateObject(tile, callback, scope)) {
                result = true;
            }
        }
    }
    
    return result;
};

/**
 * Evaluates and resolves collision between the tilemap layer and the hitbox of the members of the specified group object.
 *
 * @param {rune.display.DisplayGroup} group The group to evaluate.
 * @param {Function} [callback] Executed for each detected collision.
 * @param {Object} [scope] Scope of execution for the callback method.
 *
 * @returns {boolean}
 */
rune.tilemap.TilemapLayer.prototype.hitTestAndSeparateGroup = function(group, callback, scope) {
    var result = false;
    group.forEachMember(function(member) {
        if (this.hitTestAndSeparateObject(member, callback, scope)) {
            result = true;
        }
    }, this);
    
    return result;
};

/**
 * Sets a tile value at a specified index.
 *
 * @param {number} i Index.
 * @param {number} v Value.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.setTileValueAt = function(i, v) {
    this.m_data[i] = parseInt(v, 10);
};

/**
 * Sets a tile value for all tiles that fit within a specific rectangular area.
 *
 * @param {number} x The x-position of the area.
 * @param {number} y The y-position of the area.
 * @param {number} w The width of the area.
 * @param {number} h The height of the area.
 * @param {number} v The value to be set.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.setTileValueIn = function(x, y, w, h, v) {
    var t = this.getTileIndexesIn(x, y, w, h);
    for (var i = 0; i < t.length; i++) {
        this.setTileValueAt(t[i], v);
    }
};

/**
 * Sets a tile value for all tiles that fit within a specific rectangular area, 
 * where the area is represented by a Rectangle object.
 *
 * @param {rune.geom.Rectangle} r Rectangle object.
 * @param {number} v The value to be set.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.setTileValueInRect = function(r, v) {
    return this.setTileValueIn(
        r['x'],
        r['y'],
        r['width'],
        r['height'],
        v
    );
};

/**
 * Sets a tile value for a tile at a specific position.
 *
 * @param {number} x The x-coordinate of the position.
 * @param {number} y The y-coordinate of the position.
 * @param {number} v The value to be set.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.setTileValueOf = function(x, y, v) {
    var td = this.m_data;
    var ti = this.getTileIndexOf(x, y);
    
    td[ti] = v;
};

/**
 * Sets a tile value for a tile at a specific position, where the position consists of a Point object.
 *
 * @param {rune.geom.Point} p Point object.
 * @param {number} v The value to be set.
 *
 * @returns {undefined}
 */
rune.tilemap.TilemapLayer.prototype.setTileValueOfPoint = function(p, v) {
    this.setTileValueOf(
        p.x,
        p.y,
        v
    );
};

//------------------------------------------------------------------------------
// Public prototype methods (ENGINE)
//------------------------------------------------------------------------------

/**
 * Preparing the object for deletion.
 *
 * @returns {undefined}
 * @ignore
 */
rune.tilemap.TilemapLayer.prototype.dispose = function() {
    this.clear();
    this.m_data = null;
    this.m_map = null;
    this.m_tmpTile = null;
};

//------------------------------------------------------------------------------
// Protected prototype methods
//------------------------------------------------------------------------------

/**
 * The class constructor.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.tilemap.TilemapLayer.prototype.m_construct = function() {
    this.m_constructData();
};

//------------------------------------------------------------------------------
// Private prototype methods
//------------------------------------------------------------------------------

/**
 * Loading data.
 *
 * @returns {undefined}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.m_constructData = function() {
    if (this.m_data == null) {
        this.clear();
    } else {
        if ((this.m_data.length > 0) && (this.m_data.length !== this.m_map['numTiles'])) {
            throw new Error("Invalid map data.");
        }
    }    
};

/**
 * Calculates the distance between a start and destination index.
 *
 * @param {number} si Start index.
 * @param {number} gi Target index.
 * @param {boolean} [md=false] Whether the distance may be calculated with diagonal movement.
 *
 * @returns {Array.<number>}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.m_computeDistance = function(si, gi, md) {
    var wt = this.m_map['widthInTiles'];
    var ht = this.m_map['heightInTiles'];
    var ms = this.m_map['numTiles'];
    var ad = Array(ms);
    var td = 1;
    var tn = [si];
    var ca = null;
    var ci = 0;
    var cl = 0;
    var ni = 0;
    var dl = false;
    var dr = false;
    var du = false;
    var dd = false;
    var fe = false;
    
    var ti = 0;
    var tt = null;
    
    while(ti < ms) {
        tt = this.getTileAt(ti);
        
        if ((tt) && (tt['allowCollisions'] > 0)) {
            ad[ti] = -2;
        } else {
            ad[ti] = -1;
        }
            
        ti++;
    }
    
    ad[si] = 0;
    
    while(tn.length > 0) {
        ca = tn;
        tn = [];
        
        ti = 0;
        cl = ca.length;
        
        while (ti < cl) {
            ci = ca[ti++];
            if (ci == gi) {
                fe = true;
                ca.length = 0;
                break;
            }
            
            dl = (ci % wt > 0);
            dr = (ci % wt < wt - 1);
            du = (ci / wt > 0);
            dd = (ci / wt < ht - 1);
              
            if (du) {
                ni = ci - wt;
                if (ad[ni] == -1) {
                    ad[ni] = td;
                    tn.push(ni);
                }
            }
            
            if (dr) {
                ni = ci + 1;
                if (ad[ni] == -1) {
                    ad[ni] = td;
                    tn.push(ni);
                }
            }
            
            if (dd) {
                ni = ci + wt;
                if (ad[ni] == -1) {
                    ad[ni] = td;
                    tn.push(ni);
                }
            }
            
            if (dl) {
                ni = ci - 1;
                if (ad[ni] == -1) {
                    ad[ni] = td;
                    tn.push(ni);
                }
            }
            
            if (md == true) {
                
                if (du && dr) {
                    ni = ci - wt + 1;
                    if ((ad[ni] == -1) && (ad[ci - wt] >= -1) && (ad[ci + 1] >= -1)) {
                        ad[ni] = td;
                        tn.push(ni);
                    }
                }
                
                if (dr && dd) {
                    ni = ci + wt + 1;
                    if ((ad[ni] == -1) && (ad[ci + wt] >= -1) && (ad[ci + 1] >= -1)) {
                        ad[ni] = td;
                        tn.push(ni);
                    }
                }
                
                if (dl && dd) {
                    ni = ci + wt - 1;
                    if ((ad[ni] == -1) && (ad[ci + wt] >= -1) && (ad[ci - 1] >= -1)) {
                        ad[ni] = td;
                        tn.push(ni);
                    }
                }
                
                if (du && dl) {
                    ni = ci - wt - 1;
                    if ((ad[ni] == -1) && (ad[ci - wt] >= -1) && (ad[ci - 1] >= -1)) {
                        ad[ni] = td;
                        tn.push(ni);
                    }
                }
            }
        }
        
        td++;
    }
    
    if (!fe) {
        ad = null;
    }
        
    return ad;
};

/**
 * Recursive function that walks through the grid backwards, with the aim of finding 
 * the shortest path back to the starting position.
 *
 * @param {Array} ad Distance array.
 * @param {number} si Start index.
 * @param {Array} ap Path array.
 * @param {boolean} [md=false] Whether diagonal calculations should be used.
 *
 * @returns {undefined}
 * @private
 */
rune.tilemap.TilemapLayer.prototype.m_walk = function(ad, si, ap, md) {
    var wt = this.m_map['widthInTiles'];
    var ht = this.m_map['heightInTiles'];
    var tw = this.m_map['tileWidth'];
    var th = this.m_map['tileHeight'];
    
    ap.push(new rune.geom.Point(
        parseInt(si % wt, 10) * tw + tw * 0.5, 
        parseInt(si / wt, 10) * th + th * 0.5
    ));
    
    if (ad[si] == 0) return;
    
    var dl = (si % wt > 0);
    var dr = (si % wt < wt - 1);
    var du = (si / wt > 0);
    var dd = (si / wt < ht - 1);
    
    var cd = ad[si];
    var ci = 0;
    
    if (du) {
        ci = si - wt;
        if ((ad[ci] >= 0) && (ad[ci] < cd)) {
            this.m_walk(ad, ci, ap, md);
            return;
        }
    }
    
    if (dr) {
        ci = si + 1;
        if ((ad[ci] >= 0) && (ad[ci] < cd)) {
            this.m_walk(ad, ci, ap, md);
            return;
        }
    }
    
    if (dd) {
        ci = si + wt;
        if((ad[ci] >= 0) && (ad[ci] < cd)) {
            this.m_walk(ad, ci, ap, md);
            return;
        }
    }
    
    if (dl) {
        ci = si - 1;
        if ((ad[ci] >= 0) && (ad[ci] < cd)) {
            this.m_walk(ad, ci, ap, md);
            return;
        }
    }
    
    if (md == true) {
        
        if (du && dr) {
            ci = si - wt + 1;
            if ((ad[ci] >= 0) && (ad[ci] < cd)) {
                this.m_walk(ad, ci, ap, md);
                return;
            }
        }
        
        if (dr && dd) {
            ci = si + wt + 1;
            if ((ad[ci] >= 0) && (ad[ci] < cd)) {
                this.m_walk(ad, ci, ap, md);
                return;
            }
        }
        
        if (dl && dd) {
            ci = si + wt - 1;
            if ((ad[ci] >= 0) && (ad[ci] < cd)) {
                this.m_walk(ad, ci, ap, md);
                return;
            }
        }
        
        if (du && dl) {
            ci = si - wt - 1;
            if ((ad[ci] >= 0) && (ad[ci] < cd)) {
                this.m_walk(ad, ci, ap, md);
                return;
            }
        }   
    }        
};