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;
});