Source: util/Path.js

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

/**
 * Instantiates a new object of the class.
 *
 * @constructor
 *
 * @param {Array.<rune.geom.Point>} [points] A list of points to represent the path.
 *
 * @class
 * @classdesc
 *
 * The rune.util.Path object represents a path in the form of a list of 
 * coordinates, where each coordinate is represented by a Point object. The 
 * class is used, for example, to represent possible paths through a tilemap 
 * layer.
 *
 * @see rune.tilemap.TilemapLayer
 */
rune.util.Path = function(points) {
    
    //--------------------------------------------------------------------------
    // Public properties
    //--------------------------------------------------------------------------
    
    /**
     * Path transparency (used in debug rendering).
     *
     * @type {number}
     * @default 1
     */
    this.alpha = 1;
    
    /**
     * Color to represent the path when it is debug-rendered.
     *
     * @type {string}
     * @default #FFFFFF
     */
    this.color = "#FFFFFF";
    
    /**
     * The thickness of the path when it is debug-rendered.
     *
     * @type {number}
     * @default 1
     */
    this.thickness = 1;
    
    //--------------------------------------------------------------------------
    // Protected properties
    //--------------------------------------------------------------------------
    
    /**
     * The coordinates that make up the path.
     *
     * @type {Array.<rune.geom.Point>}
     * @protected
     * @ignore
     */
    this.m_nodes = points || [];
};

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

/**
 * Reference to the first point in the path.
 *
 * @member {rune.geom.Point} first
 * @memberof rune.util.Path
 * @instance
 * @readonly
 */
Object.defineProperty(rune.util.Path.prototype, "first", {
    /**
     * @this rune.util.Path
     * @ignore
     */
    get : function() {
        if (this.m_nodes.length > 0) {
            return this.m_nodes[0];
        }
            
        return null;
    }
});

/**
 * Reference to the last point in the path.
 *
 * @member {rune.geom.Point} last
 * @memberof rune.util.Path
 * @instance
 * @readonly
 */
Object.defineProperty(rune.util.Path.prototype, "last", {
    /**
     * @this rune.util.Path
     * @ignore
     */
    get : function() {
        if (this.m_nodes.length > 0) {
            return this.m_nodes[this.m_nodes.length - 1];
        }
            
        return null;
    }
});

/**
 * The number of points that make up the path.
 *
 * @member {number} length
 * @memberof rune.util.Path
 * @instance
 * @readonly
 */
Object.defineProperty(rune.util.Path.prototype, "length", {
    /**
     * @this rune.util.Path
     * @ignore
     */
    get : function() {
        return this.m_nodes.length;
    }
});

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

/**
 * Extends the path by one point (x and y coordinates).
 *
 * @param {number} x The x-coordinate of the point.
 * @param {number} y The y-coordinate of the point.
 *
 * @returns {undefined}
 */
rune.util.Path.prototype.add = function(x, y) {
    this.m_nodes.push(new rune.geom.Point(x, y));
};

/**
 * Adds a point to the path at a specified index.
 *
 * @param {number} x The x-coordinate of the point.
 * @param {number} y The y-coordinate of the point.
 * @param {number} index Index where the point is to be stored.
 *
 * @returns {undefined}
 */
rune.util.Path.prototype.addAt = function(x, y, index) {
    if (index > this.m_nodes.length) {
        index = this.m_nodes.length;
    }
    
    this.m_nodes.splice(index, 0, new rune.geom.Point(x,y));
};

/**
 * Extends the path by one point.
 *
 * @param {rune.geom.Point} point Point to add.
 * @param {boolean} [asReference=false] Whether the point should be added as a reference or a new object.
 *
 * @returns {undefined}
 */
rune.util.Path.prototype.addPoint = function(point, asReference) {
    if (asReference === true) {
        this.m_nodes.push(point); 
    } else {
       this.m_nodes.push(new rune.geom.Point(
           point['x'],
           point['y']
       ));
    }
};

/**
 * Adds a point to the path at a specified index.
 *
 * @param {rune.geom.Point} point Point to add.
 * @param {number} index Index where the point is to be stored.
 * @param {boolean} [asReference=false] Whether the point should be added as a reference or a new object.
 *
 * @returns {undefined}
 */
rune.util.Path.prototype.addPointAt = function(point, index, asReference) {
    if (index > this.m_nodes.length) {
        index = this.m_nodes.length;
    }
    
    if (asReference === true) {
        this.m_nodes.splice(index, 0, point);
    } else {
        this.m_nodes.splice(index, 0, new rune.geom.Point(
            point['x'], point['y']
        ));
    }
};

/**
 * Compress a path by removing unnecessary nodes (points). A compressed path 
 * represents the same path as an uncompressed path, but with fewer nodes.
 *
 * @returns {undefined}
 */
rune.util.Path.prototype.compress = function() {
    var ap = this.m_nodes;
    var dp;
    var dn;
    
    var lp = ap[0];
    var cp = null;
    var pi = 1;
    var pl = ap.length - 1;
    
    while (pi < pl) {
        cp = ap[pi];
        dp = (cp['x'] - lp['x']) / (cp['y'] - lp['y']);
        dn = (cp['x'] - ap[pi + 1]['x']) / (cp['y'] - ap[pi + 1]['y']);
        
        if ((lp['x'] == ap[pi + 1]['x']) || (lp['y'] == ap[pi + 1]['y']) || (dp == dn)) {
            ap[pi] = null;
        } else {
           lp = cp; 
        }
        
        pi++;
    }
    
    this.m_nodes = ap.filter(Boolean);
};

/**
 * Returns a point based on the specified index.
 *
 * @param {number} index Index of the point to be retuned.
 *
 * @returns {rune.geom.Point}
 */
rune.util.Path.prototype.getAt = function(index) {
    if (index > this.m_nodes.length) {
        index = this.m_nodes.length;
    }
    
    if (index < 0) {
        index = 0;
    }
    
    return this.m_nodes[index];
};

/**
 * Removes a point by reference.
 *
 * @param {rune.geom.Point} point Reference to the point to be removed.
 *
 * @returns {rune.geom.Point} Reference to the removed point.
 */
rune.util.Path.prototype.remove = function(point) {
    var index = this.m_nodes.indexOf(point);
    if (index > -1) {
        return this.m_nodes.splice(index, 1)[0];
    } else return null;
};

/**
 * Removes a point from the path. The removal is based on an index.
 *
 * @param {number} index Index of the point to remove.
 *
 * @returns {rune.geom.Point} Reference to the removed point.
 */
rune.util.Path.prototype.removeAt = function(index) {
    if (this.m_nodes.length <= 0) {
        return null;
    }
        
    if (index >= this.m_nodes.length) {
        index  = this.m_nodes.length - 1;
    }
        
    return this.m_nodes.splice(index, 1)[0];
};