Source: common/models/contour.js

define(["lib/jquery", "lib/underscore", "common/util/binding_list"],
function($,            _,                BindingList) {

    "use strict";

    var douglasPeuker = function(pts, tolerance) {
        var segment = [pts[0], pts[pts.length-1]];
        var ret;

        //get point at max distance from segment
        var maxDist = 0;
        var furthestPoint = -1;
        for (var p = 0; p<pts.length; p++){
            if (pts[p] !== segment[0] && pts[p] !== segment[1]){
                var dist = distancePointToSegment(pts[p], segment);
                if (dist > maxDist){
                    maxDist = dist;
                    furthestPoint = p;
                }
            }
        }

        if (furthestPoint !== -1 && maxDist > tolerance){
            //point distance exceeds threshold, recurse
            var t1 = douglasPeuker(pts.slice(0, furthestPoint+1), tolerance);
            var t2 = douglasPeuker(pts.slice(furthestPoint+1, pts.length), tolerance);
            ret = [];
            if (t1.length === 1){
                ret.push(t1[0]);
            } else {
                // add points 0 ... end-1
                for(var i1 = 0; i1<t1.length-1; i1++){
                    ret.push(t1[i1]);
                }
            }

            for(var i2 = 0; i2<t2.length; i2++){
                ret.push(t2[i2]);
            }

            return ret;

        } else {
            //return the segment
            ret = [];
            ret.push(segment[0]);
            ret.push(segment[1]);
            return ret;
        }

    };

    var distancePointToSegment = function(point, segment){
        var k = (segment[1][1] - segment[0][1])/(segment[1][0] - segment[0][0]);
        var m = (segment[0][1])-(k*segment[0][0]);
        return Math.abs(k*point[0] - point[1] + m)/Math.sqrt(Math.pow(k,2)+1);
    };

    /**
     * @privconstructor
     * @class A contour found by a {@link Lens.ContourSearch}
     *
     * @property {String}       id                     The ID of this contour. Read-only.
     * @property {Array}        points                 Array of x,y-coordinates of points in contour. Read-only.
     * @property {Object}       center                 Center of mass. Read-only.
     * @property {Number}       center.x               Center of mass in X dimension.
     * @property {Number}       center.y               Center of mass in Y dimension.
     * @property {Number}       center.z               The Z dimension value for the center of mass. (as distance from model)
     * @property {Object}       moments                Image moments of the contour. (see: http://en.wikipedia.org/wiki/Image_moment) Read-only.
     * @property {Array}        contourChildren        All the child contours of this one.
     * @property {Number}       contourClassification  Gesture classification.
     *
     * @name Contour
     */
    var Contour = function(id, points, cm, moments_, contourChildren, contourClassification){
        /** @alias Contour.prototype */
        var self = {};

        var moveSubscribers = BindingList();
        var endSubscribers  = BindingList();

        var center = (cm === undefined) ? {} : cm;
        var moments = moments_;
        var classification = contourClassification;
        var children = [];
        if (contourChildren!==undefined) {
            children = contourChildren.map(function(child) {
                return Contour(child.id, child.points, child.cm, child.moments, child.children, child.classification);
            });
        }

        Object.freeze(center);
        Object.freeze(points);
        Object.freeze(moments);
        Object.freeze(children);

        /**
         * Updates the points in the contour
         * @param {Array}  newPoints          The new points in the contour.
         * @param {Object} newCm              The new center of mass object with x,y,[z] coordinates
         * @param {Object} newMoments         The new images moments of this contour.
         * @param {Array}  newChildren        The new children for this contour.
         * @param {Number} newClassification  The new gesture classification.
         * @private
         */
        self._update = function(newPoints, newCm, newMoments, newChildren, newClassification){
            points = newPoints;
            center = (newCm === undefined) ? {} : newCm;
            moments = newMoments;
            classification = newClassification;
            var children = [];
            if (newChildren!==undefined) {
                children = newChildren.map(function(child) {
                    return Contour(child.id, child.points, child.cm, child.moments, child.children, child.classification);
                });
            }
            Object.freeze(center);
            Object.freeze(points);
            Object.freeze(moments);
            Object.freeze(children);
            moveSubscribers.callAll(self);
        };

        /**
         * Marks this contours as having disappeared.
         * @private
         */
        self._end = function (){
            endSubscribers.callAll(self);
        };

        /**
         * Registers a function to be called whenever this contour moves.
         * @param  {Function} fn      The function to call. Gets this contour as an
         *                            argument.
         * @return {Binding}          A Binding that allows this handler to be
         *                            cleared
         */
        self.moved = function(fn){
            moveSubscribers.add(fn);
        };

        /**
         * Registers a function to be called whenever this contour disappears from screen.
         * @param  {Function} fn      The function to call. Gets this contour as an
         *                            argument.
         * @return {Binding}          A Binding that allows this handler to be
         *                            cleared
         */
        self.disappeared = function(fn){
            endSubscribers.add(fn);
        };

        /**
         * Returns the points for the contour, simplified by the
         * Ramer–Douglas–Peucker algorithm:
         * http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
         *
         * @param {Number} [epsilon] Tolerance factor. The lower this is, the
         *                           more points will be dicarded. Defaults to
         *                           5.
         */
        self.simplifiedPoints = function(epsilon) {
            return douglasPeuker(points, (epsilon || 5));
        };

        self.id = id;

        // define getters
        Object.defineProperty(self, "points",         {get: function() { return points; }});
        Object.defineProperty(self, "center",         {get: function() { return center; }});
        Object.defineProperty(self, "moments",        {get: function() { return moments; }});
        Object.defineProperty(self, "classification", {get: function() { return classification; }});
        Object.defineProperty(self, "children",       {get: function() { return children; }});

        Object.freeze(self);
        return self;
    };

    return Contour;
});