You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1326 lines
47 KiB
Plaintext
1326 lines
47 KiB
Plaintext
11 months ago
|
/**
|
||
|
*@namespace
|
||
|
**/
|
||
|
var Util = {
|
||
|
/**Return the bounds for a given set of points, useful as every class uses a
|
||
|
* similar implementation.
|
||
|
*@param {Array<Point>} points - the points collected around the outside of
|
||
|
* the shape.
|
||
|
*@return {Array<Number>} - returns [minX, minY, maxX, maxY] - bounds, where
|
||
|
* all points are in the bounds.
|
||
|
*@author Maxim Georgievskiy <max.kharkov.ua@gmail.com>
|
||
|
**/
|
||
|
getBounds: function (points) {
|
||
|
if (!points.length)
|
||
|
return null;
|
||
|
var minX = points[0].x;
|
||
|
var maxX = minX;
|
||
|
var minY = points[0].y;
|
||
|
var maxY = minY;
|
||
|
for (var i = 1; i < points.length; i++) {
|
||
|
minX = Math.min(minX, points[i].x);
|
||
|
minY = Math.min(minY, points[i].y);
|
||
|
maxX = Math.max(maxX, points[i].x);
|
||
|
maxY = Math.max(maxY, points[i].y);
|
||
|
}
|
||
|
return [minX, minY, maxX, maxY];
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Converts an RGB color value to HSL. Conversion formula
|
||
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||
|
* Assumes r, g, and b are contained in the set [0, 255] and
|
||
|
* returns h, s, and l in the set [0, 1].
|
||
|
*
|
||
|
* @param {Number} r - The red color value
|
||
|
* @param {Number} g - The green color value
|
||
|
* @param {Number} b - The blue color value
|
||
|
* @return {Array<Number>} - the HSL representation
|
||
|
*
|
||
|
* Taken from: http://stackoverflow.com/a/9493060
|
||
|
*/
|
||
|
rgbToHsl: function (r, g, b) {
|
||
|
r /= 255;
|
||
|
g /= 255;
|
||
|
b /= 255;
|
||
|
|
||
|
var max = Math.max(r, g, b);
|
||
|
var min = Math.min(r, g, b);
|
||
|
var h, s, l = (max + min) / 2;
|
||
|
|
||
|
if (max === min) {
|
||
|
h = s = 0; // achromatic
|
||
|
} else {
|
||
|
var d = max - min;
|
||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||
|
switch (max) {
|
||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||
|
case g: h = (b - r) / d + 2; break;
|
||
|
case b: h = (r - g) / d + 4; break;
|
||
|
}
|
||
|
h /= 6;
|
||
|
}
|
||
|
|
||
|
return [h, s, l];
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Converts hex representation of RGB color (#33ffee) to object [r, g, b],
|
||
|
* where r, g, b values are contained in the set [0, 255].
|
||
|
*
|
||
|
* @param {String} hex - Hex representation of rgb color
|
||
|
* @return {Object} - in a form of
|
||
|
* {
|
||
|
* r - red color value,
|
||
|
* g - green color value,
|
||
|
* b - blue color value
|
||
|
* }
|
||
|
*
|
||
|
* Taken from: http://stackoverflow.com/a/5624139
|
||
|
*/
|
||
|
hexToRgb: function (hex) {
|
||
|
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||
|
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||
|
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
|
||
|
return r + r + g + g + b + b;
|
||
|
});
|
||
|
|
||
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||
|
return result ? {
|
||
|
r: parseInt(result[1], 16),
|
||
|
g: parseInt(result[2], 16),
|
||
|
b: parseInt(result[3], 16)
|
||
|
} : null;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Converts hsl representation array [h,s,l] contained in the set [0,1]
|
||
|
* to css-applicable string, like '(14%,80%,75%)'
|
||
|
*
|
||
|
* @param {Array<Number>} hsl - Hsl representation array [h,s,l]
|
||
|
* @return {String} - css-applicable string of hsl
|
||
|
* @author Arty
|
||
|
*/
|
||
|
hslToString: function (hsl) {
|
||
|
return 'hsl(' + hsl[0] * 360 + ', ' + hsl[1] * 100 + '%, ' + hsl[2] * 100 + '%)';
|
||
|
},
|
||
|
|
||
|
|
||
|
/***/
|
||
|
getUnionBounds: function (shapes) {
|
||
|
//tODo
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* See if some bounds are inside other bounds.
|
||
|
* Bounds are in form [minX, minY, maxX, maxY]
|
||
|
* @param {Array<Number>} innerBounds the inner bounds
|
||
|
* @param {Array<Number>} outerBounds the outer bounds
|
||
|
* @return {Boolean} true if innerBounds are inside outerBounds
|
||
|
* */
|
||
|
areBoundsInBounds: function (innerBounds, outerBounds) {
|
||
|
return (outerBounds[0] <= innerBounds[0] && (innerBounds[0] <= outerBounds[2]))
|
||
|
&& (outerBounds[1] <= innerBounds[1] && (innerBounds[1] <= outerBounds[3]))
|
||
|
&& (outerBounds[0] <= innerBounds[2] && (innerBounds[2] <= outerBounds[2]))
|
||
|
&& (outerBounds[1] <= innerBounds[3] && (innerBounds[3] <= outerBounds[3]));
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns a Polygon out of an Array of points
|
||
|
*@param {Array} data - [minX, minY, maxX, maxY]
|
||
|
**/
|
||
|
boundsToPolygon: function (data) {
|
||
|
var poly = new Polygon();
|
||
|
poly.addPoint(new Point(data[0], data[1]));
|
||
|
poly.addPoint(new Point(data[2], data[1]));
|
||
|
poly.addPoint(new Point(data[2], data[3]));
|
||
|
poly.addPoint(new Point(data[0], data[3]));
|
||
|
|
||
|
return poly;
|
||
|
},
|
||
|
|
||
|
/**Updates first letter of a string
|
||
|
*@param {String} string - the actual string
|
||
|
*@return {String} the string with first letter capitalized
|
||
|
*@see <a href="http://STACKoverflow.com/questions/1026069/capitalize-first-letter-of-string-in-javascript">http://STACKoverflow.com/questions/1026069/capitalize-first-letter-of-string-in-javascript</a>
|
||
|
**/
|
||
|
capitaliseFirstLetter: function (string) {
|
||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Increase the area of a rectangle by size in any direction
|
||
|
*@param {Array} rectangle - the [topX, topY, bottomX, bottomY]
|
||
|
*@param {Number} size - the size to increase the rectangle in any direction
|
||
|
*@return {Array} - the new reactangle increased :)
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
feather: function (rectangle, size) {
|
||
|
return [rectangle[0] + size, rectangle[1] + size, rectangle[2] + size, rectangle[3] + size];
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the distance between 2 points
|
||
|
*@param {Point} p1 - first {Point}
|
||
|
*@param {Point} p2 - second {Point}
|
||
|
*@return {Number} - the distance between those 2 points. It is always positive.
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
distance: function (p1, p2) {
|
||
|
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Find the location of a point located on segment [p1,p2] at a certain distance from p1
|
||
|
*@param {Point} p1 - first {Point}
|
||
|
*@param {Point} p2 - second {Point}
|
||
|
*@param {Number} distance_from_p1 - the distance from P1 toward P2 where searched point should be
|
||
|
*@return {Point} - the distance between those 2 points. It is always positive.
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
point_on_segment: function (p1, p2, distance_from_p1) {
|
||
|
var d = Util.distance(p1, p2);
|
||
|
var Xm = p1.x + distance_from_p1 / d * (p2.x - p1.x);
|
||
|
var Ym = p1.y + distance_from_p1 / d * (p2.y - p1.y);
|
||
|
|
||
|
return new Point(Xm, Ym);
|
||
|
},
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Creates a set of dashes/dots/etc along a set of points
|
||
|
* points = [p1, p2,p3]
|
||
|
* pt = [10,2,2 4,7] /*the pattern 10 dotts, 2 spaces, 2 dots, 7 spaces, etc
|
||
|
*
|
||
|
* @param {Context} ctx - Canvas' 2D context
|
||
|
* @param {Array} points - an {Array} of {Point}s
|
||
|
* @param {Array} pattern - an {Array} of {Integer}s that define the pattern.
|
||
|
* Pattern is increased/decreased to accomodate the lineWidth
|
||
|
* Ex: Scale whole pattern by line width (ex: if lineWidth = 2 then all variables in patter got multiplied by 2)
|
||
|
*
|
||
|
* @author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
*/
|
||
|
decorate: function (ctx, points, pattern) {
|
||
|
|
||
|
/*Algorithm:
|
||
|
*We begin with first segment and start applying the pattern as many time
|
||
|
*as we can on it. If we complete the segment then we move to next segment
|
||
|
*but we have to keep the rest of the pattern (that were not painted yet) and
|
||
|
*apply on the next segment (or segments). And so on.
|
||
|
**/
|
||
|
|
||
|
function info(msg) {
|
||
|
// console.info(msg);
|
||
|
}
|
||
|
|
||
|
function group(name) {
|
||
|
// console.group(name);
|
||
|
}
|
||
|
|
||
|
function groupEnd() {
|
||
|
// console.groupEnd();
|
||
|
}
|
||
|
|
||
|
var t0 = (new Date).getMilliseconds();
|
||
|
|
||
|
/**Scale the pattern up/down to fit the lineWidth*/
|
||
|
var pt = [];
|
||
|
|
||
|
for (var i = 0; i < pattern.length; i++) {
|
||
|
pt[i] = pattern[i] * ctx.lineWidth;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
*@param {Point} p - the {Point}
|
||
|
**/
|
||
|
function lineTo(p) {
|
||
|
ctx.lineTo(p.x, p.y);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*@param {Point} p - the {Point}
|
||
|
**/
|
||
|
function moveTo(p) {
|
||
|
ctx.moveTo(p.x, p.y);
|
||
|
}
|
||
|
|
||
|
var current_point = points[0];
|
||
|
//path = 0
|
||
|
i = 0; //current point/segment
|
||
|
var pt_i = 0; //index position in pattern
|
||
|
var pt_left = pt[0]; // spaces or dotts left to paint from current index position in pattern
|
||
|
|
||
|
info("current_point" + current_point);
|
||
|
|
||
|
//position at the begining
|
||
|
moveTo(current_point);
|
||
|
|
||
|
|
||
|
|
||
|
while (i < points.length - 1) {
|
||
|
//inside [Pi, Pi+1] segment
|
||
|
var segment_path = 0; //how much of current segment was painted
|
||
|
|
||
|
group("Paint segment " + i);
|
||
|
info("i = " + i + " current_point = " + current_point + " pt_i = " + pt_i + " pt_left = " + pt_left + " segment_path = " + segment_path);
|
||
|
|
||
|
if (pt_left < 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
//paint previous/left part of pattern
|
||
|
if (pt_left > 0) {
|
||
|
group("Paint rest of pattern");
|
||
|
info("Pattern left, pt_left : " + pt_left + " pt_i : " + pt_i);
|
||
|
|
||
|
//are we about to cross to another segment?
|
||
|
if (pt_left > Util.distance(current_point, points[i + 1])) { //we exceed current segment
|
||
|
info("We exceed current segment");
|
||
|
|
||
|
//paint what is left and move to next segment
|
||
|
if (pt_i % 2 == 0) { //dots
|
||
|
lineTo(points[i + 1])
|
||
|
}
|
||
|
else { //spaces
|
||
|
moveTo(points[i + 1])
|
||
|
}
|
||
|
|
||
|
//store what was left unpainted
|
||
|
segment_path += Util.distance(current_point, points[i + 1]);
|
||
|
pt_left = pt_left - Util.distance(current_point, points[i + 1]);
|
||
|
current_point = points[i + 1];
|
||
|
i++; //move to next segment
|
||
|
groupEnd(); //end inner group
|
||
|
groupEnd(); //end outer group
|
||
|
continue;
|
||
|
}
|
||
|
else { //still inside segment
|
||
|
info("Painting from rest path pt_i = " + pt_i + " current_segment = " + segment_path);
|
||
|
var newP = Util.point_on_segment(current_point, points[i + 1], pt_left); //translate on current_point with pt_left from Pi to Pi+1
|
||
|
info("\t newP = " + newP);
|
||
|
if (pt_i % 2 == 0) { //dots
|
||
|
lineTo(newP)
|
||
|
}
|
||
|
else { //spaces
|
||
|
moveTo(newP)
|
||
|
}
|
||
|
|
||
|
segment_path += Util.distance(current_point, newP);
|
||
|
current_point = newP;
|
||
|
pt_left = 0;
|
||
|
pt_i = (pt_i + 1) % pt.length;
|
||
|
}
|
||
|
groupEnd();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*We should have:
|
||
|
pt_i >= 0
|
||
|
pt_left = 0;
|
||
|
*/
|
||
|
group('No rest left, normal paint');
|
||
|
info("We should have (pt_i >= 0) and (pt_left = 0) AND WE HAVE " + "pt_i = " + pt_i + " pt_left = " + pt_left);
|
||
|
|
||
|
|
||
|
//nothing left from previous segment
|
||
|
while (segment_path < Util.distance(points[i], points[i + 1])) {
|
||
|
info("Distance between " + i + " and " + (i + 1) + " = " + Util.distance(points[i], points[i + 1]));
|
||
|
|
||
|
|
||
|
info("...painting path pt_i = " + pt_i + " dot/space length = " + pt[pt_i] + " current_segment = " + segment_path);
|
||
|
if (segment_path + pt[pt_i] <= Util.distance(points[i], points[i + 1])) { //still inside segment
|
||
|
group("Still inside segment");
|
||
|
var newP = Util.point_on_segment(current_point, points[i + 1], pt[pt_i]); //translate on current segment with pt[pt_i] from Pi to Pi+1
|
||
|
info("\t newP = " + newP);
|
||
|
if (pt_i % 2 == 0) {
|
||
|
lineTo(newP);
|
||
|
}
|
||
|
else {
|
||
|
moveTo(newP);
|
||
|
}
|
||
|
pt_left = 0;
|
||
|
segment_path += pt[pt_i];
|
||
|
current_point = newP;
|
||
|
pt_i = (pt_i + 1) % pt.length;
|
||
|
groupEnd();
|
||
|
}
|
||
|
else { //segment exceeded
|
||
|
group("Exceed segment");
|
||
|
if (pt_i % 2 == 0) {
|
||
|
lineTo(points[i + 1]);
|
||
|
}
|
||
|
else {
|
||
|
moveTo(points[i + 1]);
|
||
|
}
|
||
|
pt_left = pt[pt_i] - Util.distance(current_point, points[i + 1]);
|
||
|
segment_path += Util.distance(current_point, points[i + 1]);
|
||
|
current_point = points[i + 1];
|
||
|
info("...pt_left = " + pt_left + " current_segment = " + segment_path + " current_point = " + current_point);
|
||
|
//move to next segment
|
||
|
groupEnd(); //end inner group
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
groupEnd();
|
||
|
|
||
|
|
||
|
++i;
|
||
|
|
||
|
groupEnd();
|
||
|
}
|
||
|
|
||
|
var t1 = (new Date).getMilliseconds();
|
||
|
console.info("Took " + (t1 - t0) + " ms");
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Trim a number to a fixed number or decimals
|
||
|
*@param {Numeric} number - the number to be trimmed
|
||
|
*@param {Integer} decimals - the number of decimals to keep
|
||
|
*@author Zack
|
||
|
**/
|
||
|
round: function (number, decimals) {
|
||
|
return Math.round(number * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the lenght between 2 points
|
||
|
*@param {Point} startPoint - one point
|
||
|
*@param {Point} endPoint - the other point
|
||
|
*@return {Number} - the distance
|
||
|
**/
|
||
|
getLength: function (startPoint, endPoint) {
|
||
|
return Math.sqrt(Math.pow(startPoint.x - endPoint.x, 2) + Math.pow(startPoint.y - endPoint.y, 2));
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the middle point between 2 points
|
||
|
*@param {Point} startPoint - one point
|
||
|
*@param {Point} endPoint - the other point
|
||
|
*@return {Point} the middle point
|
||
|
**/
|
||
|
getMiddle: function (startPoint, endPoint) {
|
||
|
return new Point((startPoint.x + endPoint.x) / 2, (startPoint.y + endPoint.y) / 2);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the length of a Polyline that would be created with a set of points
|
||
|
*@param {Array} v - an {Array} of {Points}
|
||
|
*@return {Number} - a positive number equal with total length*/
|
||
|
getPolylineLength: function (v) {
|
||
|
var l = 0;
|
||
|
for (var i = 0; i < v.length - 1; i++) {
|
||
|
l += Util.getLength(v[i], v[i + 1]);
|
||
|
}
|
||
|
|
||
|
return l;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
*Tests if a a line defined by 2 points intersects a rectangle
|
||
|
*@param {Point} startPoint - the starting point
|
||
|
*@param {Point} endPoint - the ending point
|
||
|
*@param {Array} bounds - the bounds of the rectangle defined by (x1, y1, x2, y2)
|
||
|
*@return true - if line intersects the rectangle, false - if not
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
lineIntersectsRectangle: function (startPoint, endPoint, bounds) {
|
||
|
//create the initial line/segment
|
||
|
var l = new Line(startPoint, endPoint);
|
||
|
|
||
|
//get the 4 lines/segments represented by the bounds
|
||
|
var lines = [];
|
||
|
lines.push(new Line(new Point(bounds[0], bounds[1]), new Point(bounds[2], bounds[1])));
|
||
|
lines.push(new Line(new Point(bounds[2], bounds[1]), new Point(bounds[2], bounds[3])));
|
||
|
lines.push(new Line(new Point(bounds[2], bounds[3]), new Point(bounds[0], bounds[3])));
|
||
|
lines.push(new Line(new Point(bounds[0], bounds[3]), new Point(bounds[0], bounds[1])));
|
||
|
|
||
|
//check if our line intersects any of the 4 lines
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
if (this.lineIntersectsLine(l, lines[i])) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
*Tests if a a polyline defined by a set of points intersects a rectangle
|
||
|
*@param {Array} points - and {Array} of {Point}s
|
||
|
*@param {Array} bounds - the bounds of the rectangle defined by (x1, y1, x2, y2)
|
||
|
*@param {Boolean} closedPolyline - incase polyline is closed figure then true, else false
|
||
|
*
|
||
|
*@return true - if line intersects the rectangle, false - if not
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
*@author Janis Sejans <janis.sejans@towntech.lv>
|
||
|
**/
|
||
|
polylineIntersectsRectangle: function (points, bounds, closedPolyline) {
|
||
|
|
||
|
|
||
|
//get the 4 lines/segments represented by the bounds
|
||
|
var lines = [];
|
||
|
lines.push(new Line(new Point(bounds[0], bounds[1]), new Point(bounds[2], bounds[1])));
|
||
|
lines.push(new Line(new Point(bounds[2], bounds[1]), new Point(bounds[2], bounds[3])));
|
||
|
lines.push(new Line(new Point(bounds[2], bounds[3]), new Point(bounds[0], bounds[3])));
|
||
|
lines.push(new Line(new Point(bounds[0], bounds[3]), new Point(bounds[0], bounds[1])));
|
||
|
|
||
|
for (var k = 0; k < points.length - 1; k++) {
|
||
|
//create a line out of each 2 consecutive points
|
||
|
var tempLine = new Line(points[k], points[k + 1]);
|
||
|
|
||
|
//see if that line intersect any of the line on bounds border
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
if (this.lineIntersectsLine(tempLine, lines[i])) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//check the closed figure - that is last point connected to the first
|
||
|
if (closedPolyline) {
|
||
|
//create a line out of each 2 consecutive points
|
||
|
var tempLine = new Line(points[points.length - 1], points[0]);
|
||
|
|
||
|
//see if that line intersect any of the line on bounds border
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
if (this.lineIntersectsLine(tempLine, lines[i])) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
*Test to see if 2 {Line}s intersects. They are considered finite segments
|
||
|
*and not the infinite lines from geometry
|
||
|
*@param {Line} l1 - fist line/segment
|
||
|
*@param {Line} l2 - last line/segment
|
||
|
*@return {Boolean} true - if the lines intersect or false if not
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
*@author Maxim Georgievskiy <max.kharkov.ua@gmail.com>
|
||
|
**/
|
||
|
lineIntersectsLine: function (l1, l2) {
|
||
|
// check for two vertical lines
|
||
|
if (l1.startPoint.x == l1.endPoint.x && l2.startPoint.x == l2.endPoint.x) {
|
||
|
return l1.startPoint.x == l2.startPoint.x ? // if 'infinite 'lines do coincide,
|
||
|
// then check segment bounds for overlapping
|
||
|
l1.contains(l2.startPoint.x, l2.startPoint.y) ||
|
||
|
l1.contains(l2.endPoint.x, l2.endPoint.y) :
|
||
|
// lines are paralel
|
||
|
false;
|
||
|
}
|
||
|
// if one line is vertical, and another line is not vertical
|
||
|
else if (l1.startPoint.x == l1.endPoint.x || l2.startPoint.x == l2.endPoint.x) {
|
||
|
// let assume l2 is vertical, otherwise exchange them
|
||
|
if (l1.startPoint.x == l1.endPoint.x) {
|
||
|
var l = l1;
|
||
|
l1 = l2;
|
||
|
l2 = l;
|
||
|
}
|
||
|
// finding intersection of 'infinite' lines
|
||
|
// equation of the first line is y = ax + b, second: x = c
|
||
|
var a = (l1.endPoint.y - l1.startPoint.y) / (l1.endPoint.x - l1.startPoint.x);
|
||
|
var b = l1.startPoint.y - a * l1.startPoint.x;
|
||
|
var x0 = l2.startPoint.x;
|
||
|
var y0 = a * x0 + b;
|
||
|
return l1.contains(x0, y0) && l2.contains(x0, y0);
|
||
|
}
|
||
|
|
||
|
// check normal case - both lines are not vertical
|
||
|
else {
|
||
|
//line equation is : y = a*x + b, b = y - a * x
|
||
|
var a1 = (l1.endPoint.y - l1.startPoint.y) / (l1.endPoint.x - l1.startPoint.x);
|
||
|
var b1 = l1.startPoint.y - a1 * l1.startPoint.x;
|
||
|
|
||
|
var a2 = (l2.endPoint.y - l2.startPoint.y) / (l2.endPoint.x - l2.startPoint.x);
|
||
|
var b2 = l2.startPoint.y - a2 * l2.startPoint.x;
|
||
|
|
||
|
if (a1 == a2) { //paralel lines
|
||
|
return b1 == b2 ?
|
||
|
// for coincide lines, check for segment bounds overlapping
|
||
|
l1.contains(l2.startPoint.x, l2.startPoint.y) || l1.contains(l2.endPoint.x, l2.endPoint.y)
|
||
|
:
|
||
|
// not coincide paralel lines have no chance to intersect
|
||
|
false;
|
||
|
} else { //usual case - non paralel, the 'infinite' lines intersects...we only need to know if inside the segment
|
||
|
|
||
|
/*
|
||
|
* if one of the lines are vertical, then x0 is equal to their x,
|
||
|
* otherwise:
|
||
|
* y1 = a1 * x + b1
|
||
|
* y2 = a2 * x + b2
|
||
|
* => x0 = (b2 - b1) / (a1 - a2)
|
||
|
* => y0 = a1 * x0 + b1
|
||
|
**/
|
||
|
x0 = (b2 - b1) / (a1 - a2);
|
||
|
y0 = a1 * x0 + b1;
|
||
|
return l1.contains(x0, y0) && l2.contains(x0, y0);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Tests if 3 points are coliniar (similar to geometry ...with infinite lines)
|
||
|
*@param {Point} p1 - first point
|
||
|
*@param {Point} p2 - second point
|
||
|
*@param {Point} p3 - third point
|
||
|
*@return {Boolean} - true if coliniar and false if not
|
||
|
*@author Alex
|
||
|
*@deprecated
|
||
|
**/
|
||
|
deprecated_collinearity: function (p1, p2, p3) {
|
||
|
//Check if 2 points coincide. If they do we automatically have collinearity
|
||
|
if (p1.x === p2.x && p1.y === p2.y) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (p1.x === p3.x && p1.y === p3.y) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (p2.x === p3.x && p2.y === p3.y) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// check for vertical line
|
||
|
if (p1.x === p2.x) {
|
||
|
return p3.x === p1.x;
|
||
|
} else { // usual (not vertical) line can be represented as y = a * x + b
|
||
|
var a = (p2.y - p1.y) / (p2.x - p1.x);
|
||
|
var b = p1.y - a * p1.x;
|
||
|
return p3.y === a * p3.x + b;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Tests if 3 points are coliniar with matrix determinants.
|
||
|
* If the determinat of matrix
|
||
|
* / \
|
||
|
* | x1 y1 1 |
|
||
|
* | x2 y2 1 |
|
||
|
* | x3 y3 1 |
|
||
|
* \ /
|
||
|
* is zero it means that the points are colinear
|
||
|
*@param {Point} p1 - first point
|
||
|
*@param {Point} p2 - second point
|
||
|
*@param {Point} p3 - third point
|
||
|
*@return {Boolean} - true if coliniar and false if not
|
||
|
*@author Alex
|
||
|
*@see http://en.wikipedia.org/wiki/Determinant
|
||
|
*@see https://people.richland.edu/james/lecture/m116/matrices/applications.html
|
||
|
**/
|
||
|
collinearity: function (p1, p2, p3, precission) {
|
||
|
var determinant = (p1.x * p2.y + p1.y * p3.x + p2.x * p3.y)
|
||
|
- (p2.y * p3.x + p1.y * p2.x + p1.x * p3.y);
|
||
|
|
||
|
if (precission) {
|
||
|
return Math.abs(determinant) <= precission;
|
||
|
}
|
||
|
else {
|
||
|
return determinant === 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
},
|
||
|
|
||
|
|
||
|
/** It will return the end point of a line on a given angle (clockwise).
|
||
|
* @param {Point} startPoint - the start of the line
|
||
|
* @param {Number} length - the length of the line
|
||
|
* @param {Number} angle - the angle of the line in radians
|
||
|
* @return {Point} - the endPoint of the line
|
||
|
* @author Zack
|
||
|
*/
|
||
|
getEndPoint: function (startPoint, length, angle) {
|
||
|
var endPoint = startPoint.clone();
|
||
|
endPoint.transform(Matrix.translationMatrix(-startPoint.x, -startPoint.y));
|
||
|
endPoint.y -= length;
|
||
|
endPoint.transform(Matrix.rotationMatrix(angle));
|
||
|
endPoint.transform(Matrix.translationMatrix(startPoint.x, startPoint.y));
|
||
|
return endPoint;
|
||
|
},
|
||
|
|
||
|
|
||
|
/** Will return the angle of rotation between 2 points, with 0 being north.
|
||
|
* Actually the angle with N on a compass
|
||
|
* @param {@link Point} centerPoint - the point that is to be considered the center of the shape
|
||
|
* @param {@link Point} outsidePoint - the point that we need to find the rotation about the center.
|
||
|
* @param {Number} round - amount to round to nearest angle (optional). Think of it as precision
|
||
|
* @return {Number} - the angle in radians
|
||
|
* @see /documentation/specs/getAngle.png
|
||
|
* @author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
* */
|
||
|
getAngle: function (centerPoint, outsidePoint, round) {
|
||
|
centerPoint.x = Util.round(centerPoint.x, 5);
|
||
|
centerPoint.y = Util.round(centerPoint.y, 5);
|
||
|
outsidePoint.x = Util.round(outsidePoint.x, 5);
|
||
|
outsidePoint.y = Util.round(outsidePoint.y, 5);
|
||
|
var angle = Math.atan((outsidePoint.x - centerPoint.x) / (outsidePoint.y - centerPoint.y));
|
||
|
angle = -angle;
|
||
|
|
||
|
//endAngle+=90;
|
||
|
if (outsidePoint.x >= centerPoint.x && outsidePoint.y >= centerPoint.y) {
|
||
|
angle += Math.PI;
|
||
|
}
|
||
|
else if (outsidePoint.x <= centerPoint.x && outsidePoint.y >= centerPoint.y) {
|
||
|
angle += Math.PI;
|
||
|
}
|
||
|
else if (outsidePoint.x <= centerPoint.x && outsidePoint.y <= centerPoint.y) {
|
||
|
angle += Math.PI * 2;
|
||
|
}
|
||
|
while (angle >= Math.PI * 2) {
|
||
|
angle -= Math.PI * 2;
|
||
|
}
|
||
|
if (isNaN(angle)) {//Nan
|
||
|
angle = 0; //we are at center point;
|
||
|
}
|
||
|
if (round) {
|
||
|
angle = Math.round(angle / round) * round
|
||
|
}
|
||
|
return angle;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
*Computes the angle formed by 3 {Point}s
|
||
|
*@param {@link Point} startPoint - the start point
|
||
|
*@param {@link Point} centerPoint - the center/tid of the angle point
|
||
|
*@param {@link Point} endPoint - the end/angle point
|
||
|
*@param {Number} round - amount to round to nearest angle (optional)
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
*/
|
||
|
getAngle3Points: function (startPoint, centerPoint, endPoint, round) {
|
||
|
var a1 = Util.getAngle(centerPoint, startPoint);
|
||
|
var a2 = Util.getAngle(centerPoint, endPoint);
|
||
|
|
||
|
var angle = a2 - a1;
|
||
|
|
||
|
if (round) {
|
||
|
angle = Math.round(angle / round) * round
|
||
|
}
|
||
|
|
||
|
return angle;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
/* Tests whether a point is inside the area (excluding border) determined by a set of other points.
|
||
|
* If the points is on the border of the area it will not be counted
|
||
|
*
|
||
|
* @param point {Point} the point we want to chek
|
||
|
* @param points {Array<Point>} a set of points ordered clockwise.
|
||
|
* @see <a href="http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/">http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/</a> solution 1
|
||
|
* */
|
||
|
isPointInside: function (point, points) {
|
||
|
if (points.length < 3) {
|
||
|
return false;
|
||
|
}
|
||
|
var counter = 0;
|
||
|
var p1 = points[0];
|
||
|
|
||
|
//calulates horizontal intersects
|
||
|
for (var i = 1; i <= points.length; i++) {
|
||
|
var p2 = points[i % points.length];
|
||
|
if (point.y > Math.min(p1.y, p2.y)) { //our point is between start(Y) and end(Y) points
|
||
|
if (point.y <= Math.max(p1.y, p2.y)) {
|
||
|
if (point.x <= Math.max(p1.x, p2.x)) { //to the left of any point
|
||
|
if (p1.y != p2.y) { //no horizontal line
|
||
|
var xinters = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x; //get slope of line and make it start from the same place as p1
|
||
|
if (p1.x == p2.x || point.x <= xinters) { //if vertical line or our x is before the end x of the actual line.
|
||
|
counter++; //we have an intersection
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
p1 = p2;
|
||
|
}
|
||
|
|
||
|
if (counter % 2 == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
else {
|
||
|
return true;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
/* Tests whether a point is inside the area (including border) determined by a set of other points.
|
||
|
* If the points is on the border of the area it will be counted
|
||
|
*
|
||
|
* Algorithm: just get border (min/max) values for x and y
|
||
|
* and then check if target point inside and on a borer or outside of points
|
||
|
*
|
||
|
* @param point {Point} the point we want to check
|
||
|
* @param points {Array<Point>} a set of points ordered clockwise.
|
||
|
* */
|
||
|
isPointInsideOrOnBorder: function (point, points) {
|
||
|
if (points.length < 3) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// set min & max values to coordinates of first point
|
||
|
var minX = points[0].x;
|
||
|
var maxX = points[0].x;
|
||
|
var minY = points[0].y;
|
||
|
var maxY = points[0].y;
|
||
|
|
||
|
// go through points and get min and max x, y values
|
||
|
for (var i = 1; i < points.length; i++) {
|
||
|
var p = points[i];
|
||
|
|
||
|
minX = Math.min(p.x, minX);
|
||
|
maxX = Math.max(p.x, maxX);
|
||
|
minY = Math.min(p.y, minY);
|
||
|
maxY = Math.max(p.y, maxY);
|
||
|
}
|
||
|
|
||
|
// check if point is inside and on a border of points or outside
|
||
|
if (point.x >= minX && point.x <= maxX && point.y >= minY && point.y <= maxY) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Calculates the number of times the vector from P0(x0,y0) to P1(x1,y1)
|
||
|
* crosses the ray extending to the right from P(px,py).
|
||
|
* @return {Number}
|
||
|
* 0 - if the point lies on the line or no intersection
|
||
|
* +1 - if intersection happened and the Y coordinate is increasing (y0 < y1)
|
||
|
* -1 - if intersection happened and the Y coordinate is decreasing (y0 >= y1)
|
||
|
* @param {Number} px coordinates x for point P
|
||
|
* @param {Number} py coordinates y for point P
|
||
|
* @param {Number} x0 coordinates x for point P0
|
||
|
* @param {Number} y0 coordinates y for point P0
|
||
|
* @param {Number} x1 coordinates x for point P1
|
||
|
* @param {Number} y1 coordinates y for point P1
|
||
|
* Note: This is pretty much similar to what we have in isPointInside(...) method
|
||
|
* but this was inspired from JDK thus older and not used
|
||
|
*/
|
||
|
pointCrossingsForLine: function (px, py, x0, y0, x1, y1) {
|
||
|
if (py < y0 && py < y1) return 0;
|
||
|
if (py >= y0 && py >= y1) return 0;
|
||
|
if (px >= x0 && px >= x1) return 0;
|
||
|
// assert(y0 != y1);
|
||
|
if (y0 == y1) throw Exception('Asserted: ' + y0 + ' == ' + y1);
|
||
|
if (px < x0 && px < x1) return (y0 < y1) ? 1 : -1;
|
||
|
var xintercept = x0 + (py - y0) * (x1 - x0) / (y1 - y0);
|
||
|
if (px >= xintercept) return 0;
|
||
|
return (y0 < y1) ? 1 : -1;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Calculates the number of times the cubic from point P0(x0,y0) to point P1(x1,y1)
|
||
|
* crosses the ray extending to the right from point P(px,py).
|
||
|
* @return {Number}
|
||
|
* 0 - If the point lies on a part of the curve or no intersetion
|
||
|
* +1 - is added for each crossing where the Y coordinate is increasing
|
||
|
* -1 - is added for each crossing where the Y coordinate is decreasing
|
||
|
*
|
||
|
* @param {Number} px coordinates x for point P
|
||
|
* @param {Number} py coordinates y for point P
|
||
|
* @param {Number} x0 coordinates x for point P0 (start point)
|
||
|
* @param {Number} y0 coordinates y for point P0 (start point)
|
||
|
* @param {Number} xc0 coordinates x for point C0 (first controll point)
|
||
|
* @param {Number} yc0 coordinates y for point C0 (first controll point)
|
||
|
* @param {Number} xc1 coordinates x for point C1 (second controll point)
|
||
|
* @param {Number} yc1 coordinates y for point C1 (second controll point)
|
||
|
* @param {Number} x1 coordinates x for point P1 (end point)
|
||
|
* @param {Number} y1 coordinates y for point P1 (end point)
|
||
|
* @param {Number} level The level parameter should be 0 at the top-level
|
||
|
* call and will count up for each recursion level to prevent infinite recursion
|
||
|
* @see http://www.atalasoft.com/blogs/stevehawley/may-2013/how-to-split-a-cubic-bezier-curve
|
||
|
*/
|
||
|
pointCrossingsForCubic: function (px, py, x0, y0, xc0, yc0, xc1, yc1, x1, y1, level) {
|
||
|
if (py < y0 && py < yc0 && py < yc1 && py < y1) return 0;
|
||
|
if (py >= y0 && py >= yc0 && py >= yc1 && py >= y1) return 0;
|
||
|
// Note y0 could equal yc0...
|
||
|
if (px >= x0 && px >= xc0 && px >= xc1 && px >= x1) return 0;
|
||
|
if (px < x0 && px < xc0 && px < xc1 && px < x1) {
|
||
|
if (py >= y0) {
|
||
|
if (py < y1) return 1;
|
||
|
} else {
|
||
|
// py < y0
|
||
|
if (py >= y1) return -1;
|
||
|
}
|
||
|
// py outside of y01 range, and/or y0==yc0
|
||
|
return 0;
|
||
|
}
|
||
|
// double precision only has 52 bits of mantissa (Give up and fall back to line intersection)
|
||
|
if (level > 52) return pointCrossingsForLine(px, py, x0, y0, x1, y1);
|
||
|
|
||
|
//"split" current cubic into 2 new cubic curves
|
||
|
var xmid = (xc0 + xc1) / 2;
|
||
|
var ymid = (yc0 + yc1) / 2;
|
||
|
xc0 = (x0 + xc0) / 2;
|
||
|
yc0 = (y0 + yc0) / 2;
|
||
|
xc1 = (xc1 + x1) / 2;
|
||
|
yc1 = (yc1 + y1) / 2;
|
||
|
var xc0m = (xc0 + xmid) / 2;
|
||
|
var yc0m = (yc0 + ymid) / 2;
|
||
|
var xmc1 = (xmid + xc1) / 2;
|
||
|
var ymc1 = (ymid + yc1) / 2;
|
||
|
xmid = (xc0m + xmc1) / 2;
|
||
|
ymid = (yc0m + ymc1) / 2;
|
||
|
if (isNaN(xmid) || isNaN(ymid)) {
|
||
|
// [xy]mid are NaN if any of [xy]c0m or [xy]mc1 are NaN
|
||
|
// [xy]c0m or [xy]mc1 are NaN if any of [xy][c][01] are NaN
|
||
|
// These values are also NaN if opposing infinities are added
|
||
|
return 0;
|
||
|
}
|
||
|
return (Util.pointCrossingsForCubic(px, py, x0, y0, xc0, yc0, xc0m, yc0m, xmid, ymid, level + 1)
|
||
|
+ Util.pointCrossingsForCubic(px, py, xmid, ymid, xmc1, ymc1, xc1, yc1, x1, y1, level + 1));
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the min of a vector
|
||
|
*@param {Array} v - vector of {Number}s
|
||
|
*@return {Number} - the minimum number from the vector or NaN if vector is empty
|
||
|
*@author alex@scriptoid.com
|
||
|
**/
|
||
|
min: function (v) {
|
||
|
if (v.lenght == 0) {
|
||
|
return NaN;
|
||
|
}
|
||
|
else {
|
||
|
var m = v[0];
|
||
|
for (var i = 0; i < v.length; i++) {
|
||
|
if (m > v[i]) {
|
||
|
m = v[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Returns the max of a vector
|
||
|
*@param {Array} v - vector of {Number}s
|
||
|
*@return {Number} - the maximum number from the vector or NaN if vector is empty
|
||
|
*@author alex@scriptoid.com
|
||
|
**/
|
||
|
max: function (v) {
|
||
|
if (v.lenght == 0) {
|
||
|
return NaN;
|
||
|
}
|
||
|
else {
|
||
|
var m = v[0];
|
||
|
for (var i = 0; i < v.length; i++) {
|
||
|
if (m < v[i]) {
|
||
|
m = v[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
*Tests if a vector of points is a valid path (not going back)
|
||
|
*There are a few problems here. If you have p1, p2, p3 and p4 and p2 = p3 you need to ignore that
|
||
|
*@param {Array} v - an {Array} of {Point}s
|
||
|
*@return {Boolean} - true if path is valid, false otherwise
|
||
|
*@author Alex <alex@scriptoid.com>
|
||
|
**/
|
||
|
forwardPath: function (v) {
|
||
|
if (v.length <= 2) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < v.length - 2; i++) {
|
||
|
if (v[i].x == v[i + 1].x && v[i + 1].x == v[i + 2].x) { //on the same vertical
|
||
|
if (signum(v[i + 1].y - v[i].y) != 0) { //test only we have a progressing path
|
||
|
if (signum(v[i + 1].y - v[i].y) == -1 * signum(v[i + 2].y - v[i + 1].y)) { //going back (ignore zero)
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (v[i].y == v[i + 1].y && v[i + 1].y == v[i + 2].y) { //on the same horizontal
|
||
|
if (signum(v[i + 1].x - v[i].x) != 0) { //test only we have a progressing path
|
||
|
if (signum(v[i + 1].x - v[i].x) == -1 * signum(v[i + 2].x - v[i + 1].x)) { //going back (ignore zero)
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
*Tests if a vector of points is an orthogonal path (moving in multiples of 90 degrees)
|
||
|
*@param {Array} v - an {Array} of {Point}s
|
||
|
*@return {Boolean} - true if path is valid, false otherwise
|
||
|
*@author Alex <alex@scriptoid.com>
|
||
|
**/
|
||
|
orthogonalPath: function (v) {
|
||
|
if (v.length <= 1) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < v.length - 1; i++) {
|
||
|
if (v[i].x != v[i + 1].x && v[i].y != v[i + 1].y) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Tries to cut the unecessary poins.
|
||
|
*Ex: If you have 3 points A,B and C and they are collinear then B will be cut
|
||
|
*@param {Array} v - an {Array} of {Point}s
|
||
|
*@return {Array} - the "reduced" vector of {Point}s
|
||
|
*@author Alex <alex@scriptoid.com>
|
||
|
**/
|
||
|
collinearReduction: function (v) {
|
||
|
var r = [];
|
||
|
|
||
|
if (v.length < 3) {
|
||
|
return Point.cloneArray(v);
|
||
|
}
|
||
|
|
||
|
r.push(v[0].clone());
|
||
|
for (var i = 1; i < v.length - 1; i++) {
|
||
|
if ((v[i - 1].x == v[i].x && v[i].x == v[i + 1].x) || (v[i - 1].y == v[i].y && v[i].y == v[i + 1].y)) {
|
||
|
continue;
|
||
|
}
|
||
|
else {
|
||
|
r.push(v[i].clone());
|
||
|
}
|
||
|
}
|
||
|
r.push(v[v.length - 1].clone());
|
||
|
|
||
|
return r;
|
||
|
},
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/**Score a ortogonal path made out of Points
|
||
|
*Iterates over a set of points (minimum 3)
|
||
|
*For each 3 points (i, i+1, i+2) :
|
||
|
* - if the 3rd one is after the 2nd on the same line we add +1
|
||
|
* - if the 3rd is up or down related to the 2nd we do not do anything +0
|
||
|
* - if the 3rd goes back we imediatelly return -1
|
||
|
*@param {Array} v - an array of {Point}s
|
||
|
*@return {Number} - -1 if the path is wrong (goes back) or something >= 0 if is fine
|
||
|
* The bigger the number the smooth the path is
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
scorePath: function (v) {
|
||
|
if (v.length <= 2) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
var score = 0;
|
||
|
for (var i = 1; i < v.length - 1; i++) {
|
||
|
if (v[i - 1].x == v[i].x && v[i].x == v[i + 1].x) { //on the same vertical
|
||
|
if (signum(v[i + 1].y - v[i].y) == signum(v[i].y - v[i - 1].y)) { //same direction
|
||
|
score++;
|
||
|
}
|
||
|
else { //going back - no good
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else if (v[i - 1].y == v[i].y && v[i].y == v[i + 1].y) { //on the same horizontal
|
||
|
if (signum(v[i + 1].x - v[i].x) == signum(v[i].x - v[i - 1].x)) { //same direction
|
||
|
score++;
|
||
|
}
|
||
|
else { //going back - no good
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
else { //not on same vertical nor horizontal
|
||
|
score--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return score;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Function to be used as a replacer in JSON's stringify process.
|
||
|
* We need this as Opera does some rounding (@see https://bitbucket.org/scriptoid/diagramo/issue/35/serialization-fails-on-opera-1115)
|
||
|
* @param {String} key the property of an object
|
||
|
* @param val the value of the key
|
||
|
* @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify
|
||
|
* @see https://developer.mozilla.org/en-US/docs/Using_native_JSON#The_replacer_parameter
|
||
|
* @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toFixed
|
||
|
* @author Artyom, Alex
|
||
|
* @deprecated Since Opera 19.0.1326.47 problem dissapeared. So 0.8999999999999999.toString() no longer returns "0.9"
|
||
|
* */
|
||
|
operaReplacer: function (key, val) {
|
||
|
if (typeof (val) !== 'undefined' && val !== null) {
|
||
|
// As toFixed(...) method is specific only for Number type we will use it to test if val is actually a Number
|
||
|
if (val.toFixed) {
|
||
|
val = val.toFixed(20); //this will ensure that ANY string representation will have a . (dot) and some 0 (zero)s at the end
|
||
|
|
||
|
// check if val has decimals and it ends with zero(s)
|
||
|
if (/\.\d*0+$/.test(val)) {
|
||
|
// remove last decimal zero(s) from the end of val (and with dot if it is actually)
|
||
|
val = val.replace(/(\.)?0+$/, '');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return val;
|
||
|
|
||
|
/*by default the return will be undefined which means the 'key' will not be stringified
|
||
|
* "If you return undefined, the property is not included in the output JSON string."
|
||
|
*/
|
||
|
},
|
||
|
|
||
|
/**Creates a new primitive out of JSON parsed object
|
||
|
*@param {JSONObject} o - the JSON parsed object of primitive
|
||
|
*@return {primitive} a newly constructed primitive
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
*@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
||
|
**/
|
||
|
loadPrimitive: function (o) {
|
||
|
var result = null;
|
||
|
|
||
|
/**We can not use instanceof Point construction as
|
||
|
*the JSON objects are typeless... so JSONObject are simply objects */
|
||
|
if (o.oType == 'Point') {
|
||
|
result = Point.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Line') {
|
||
|
result = Line.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Polyline') {
|
||
|
result = Polyline.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Polygon') {
|
||
|
result = Polygon.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'DottedPolygon') {
|
||
|
result = DottedPolygon.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'QuadCurve') {
|
||
|
result = QuadCurve.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'CubicCurve') {
|
||
|
result = CubicCurve.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Arc') {
|
||
|
result = Arc.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Ellipse') {
|
||
|
result = Ellipse.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'DashedArc') {
|
||
|
result = DashedArc.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Text') {
|
||
|
result = Text.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Path') {
|
||
|
result = Path.load(o);
|
||
|
}
|
||
|
else if (o.oType == 'Figure') {
|
||
|
result = Figure.load(o); //kinda recursevly
|
||
|
}
|
||
|
else if (o.oType == 'ImageFrame') {
|
||
|
result = ImageFrame.load(o); //kinda recursevly
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
|
||
|
/** Selects an object using x and y coordinates:
|
||
|
* it can be one of: Figure, Group, Connector, Container or none.
|
||
|
*
|
||
|
* Note: Connectors are more important than Figures and Figures more important
|
||
|
* than Container so Connectors > Figures > Container
|
||
|
* @param {Number} x - the x coordinate
|
||
|
* @param {Number} y - the y coordinate
|
||
|
* @return {Object}- in a form of
|
||
|
* {
|
||
|
* id - id of object or -1 if none,
|
||
|
* type - type of object, the same as oType or '' if none
|
||
|
* }
|
||
|
* @author Arty
|
||
|
*/
|
||
|
getObjectByXY: function (x, y) {
|
||
|
//find Connector at (x,y)
|
||
|
var cId = CONNECTOR_MANAGER.connectorGetByXY(x, y);
|
||
|
if (cId != -1) { // found a Connector
|
||
|
return {
|
||
|
id: cId,
|
||
|
type: 'Connector'
|
||
|
};
|
||
|
}
|
||
|
|
||
|
//find Figure at (x,y)
|
||
|
var fId = STACK.figureGetByXY(x, y);
|
||
|
if (fId != -1) { // found a Figure
|
||
|
var gId = STACK.figureGetById(fId).groupId;
|
||
|
if (gId != -1) { // if the Figure belongs to a Group then return that Group
|
||
|
return {
|
||
|
id: gId,
|
||
|
type: 'Group'
|
||
|
}
|
||
|
}
|
||
|
else { // lonely Figure
|
||
|
return {
|
||
|
id: fId,
|
||
|
type: 'Figure'
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//find Container at (x,y)
|
||
|
var contId = STACK.containerGetByXY(x, y);
|
||
|
if (contId !== -1) { // found a Container
|
||
|
return {
|
||
|
id: contId,
|
||
|
type: 'Container'
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// none of above
|
||
|
return {
|
||
|
id: -1,
|
||
|
type: ''
|
||
|
};
|
||
|
},
|
||
|
NewGUID: function () {
|
||
|
function S4() {
|
||
|
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||
|
}
|
||
|
var newGuid = (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
|
||
|
return newGuid.toUpperCase();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**Returns the sign of a number
|
||
|
*@param {Number} x - the number
|
||
|
*@returns {Number}
|
||
|
*@see <a href="http://en.wikipedia.org/wiki/Sign_function">http://en.wikipedia.org/wiki/Sign_function</a>
|
||
|
*@author alex@scriptoid.com
|
||
|
**/
|
||
|
function signum(x){
|
||
|
if(x > 0)
|
||
|
return 1;
|
||
|
else if(x < 0)
|
||
|
return -1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/** Check if a value is numeric
|
||
|
* @param {String} input - a numeric value
|
||
|
* @see <a href="http://STACKoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric">http://STACKoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric</a>
|
||
|
* @author Zack Newsham <zack_newsham@yahoo.co.uk>
|
||
|
* */
|
||
|
function isNumeric(input){
|
||
|
return (input - 0) == input && (input.length > 0 || input != "");
|
||
|
}
|
||
|
|
||
|
/**Repeats a string for several time and return the concatenated result
|
||
|
*@param {String} str - the string
|
||
|
*@param {Integer} count - the number of time the string should be repeated
|
||
|
*@return {String}
|
||
|
**/
|
||
|
function repeat(str, count){
|
||
|
var res = '';
|
||
|
for(var i=0;i<count;i++){
|
||
|
res += str;
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set selection on target interval inside text DOM element
|
||
|
* @param {HTMLElement} input - DOM element to set selection
|
||
|
* @param {Number} selectionStart - start position of selection
|
||
|
* @param {Number} selectionEnd - end position of selection
|
||
|
* @author Artyom
|
||
|
**/
|
||
|
function setSelectionRange(input, selectionStart, selectionEnd) {
|
||
|
if (input.setSelectionRange) {
|
||
|
input.focus();
|
||
|
input.setSelectionRange(selectionStart, selectionEnd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**A simple class to detect browser and it's version using navigator properties.
|
||
|
* Computing logic can be found here: http://stackoverflow.com/a/2401861/2097494
|
||
|
*
|
||
|
* @this {Browser}
|
||
|
* @constructor
|
||
|
*
|
||
|
* @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
||
|
**/
|
||
|
function Browser() {
|
||
|
var N = navigator.appName.toLowerCase();
|
||
|
var ua = navigator.userAgent.toLowerCase();
|
||
|
var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
|
||
|
var temp = ua.match(/version\/([\.\d]+)/i);
|
||
|
if(M && temp != null) {
|
||
|
M[2]= temp[1];
|
||
|
}
|
||
|
M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
|
||
|
|
||
|
this.webkit = M[0].indexOf("chrome") > -1 || M[0].indexOf("safari") > -1;
|
||
|
this.opera = M[0].indexOf("opera") > -1;
|
||
|
this.msie = M[0].indexOf("msie") > -1;
|
||
|
this.mozilla = M[0].indexOf("firefox") > -1;
|
||
|
this.version = M[1];
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Removes DOM element
|
||
|
* @param {HTMLElement} element - DOM element to remove
|
||
|
*
|
||
|
* @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
||
|
*
|
||
|
* Note: <br/>
|
||
|
* Cross-browser solution for native JS.
|
||
|
*/
|
||
|
function removeElement(element) {
|
||
|
element && element.parentNode && element.parentNode.removeChild(element);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes NodeList
|
||
|
* @param {NodeList} list - NodeList to remove
|
||
|
*
|
||
|
* @author Artyom Pokatilov <artyom.pokatilov@gmail.com>
|
||
|
*
|
||
|
* Note: <br/>
|
||
|
* Cross-browser solution for native JS.
|
||
|
*/
|
||
|
function removeNodeList(list) {
|
||
|
var i;
|
||
|
var length = list.length;
|
||
|
|
||
|
for (i = 0; i < length; i++) {
|
||
|
removeElement(list[i]);
|
||
|
}
|
||
|
}
|