/** * Biltong v0.2 * * Various geometry functions written as part of jsPlumb and perhaps useful for others. * * Copyright (c) 2014 Simon Porritt * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ ;(function() { "use strict"; var Biltong = this.Biltong = {}; var _isa = function(a) { return Object.prototype.toString.call(a) === "[object Array]"; }, _pointHelper = function(p1, p2, fn) { p1 = _isa(p1) ? p1 : [p1.x, p1.y]; p2 = _isa(p2) ? p2 : [p2.x, p2.y]; return fn(p1, p2); }, /** * @name Biltong.gradient * @function * @desc Calculates the gradient of a line between the two points. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Float} The gradient of a line between the two points. */ _gradient = Biltong.gradient = function(p1, p2) { return _pointHelper(p1, p2, function(_p1, _p2) { if (_p2[0] == _p1[0]) return _p2[1] > _p1[1] ? Infinity : -Infinity; else if (_p2[1] == _p1[1]) return _p2[0] > _p1[0] ? 0 : -0; else return (_p2[1] - _p1[1]) / (_p2[0] - _p1[0]); }); }, /** * @name Biltong.normal * @function * @desc Calculates the gradient of a normal to a line between the two points. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Float} The gradient of a normal to a line between the two points. */ _normal = Biltong.normal = function(p1, p2) { return -1 / _gradient(p1, p2); }, /** * @name Biltong.lineLength * @function * @desc Calculates the length of a line between the two points. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Float} The length of a line between the two points. */ _lineLength = Biltong.lineLength = function(p1, p2) { return _pointHelper(p1, p2, function(_p1, _p2) { return Math.sqrt(Math.pow(_p2[1] - _p1[1], 2) + Math.pow(_p2[0] - _p1[0], 2)); }); }, /** * @name Biltong.quadrant * @function * @desc Calculates the quadrant in which the angle between the two points lies. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Integer} The quadrant - 1 for upper right, 2 for lower right, 3 for lower left, 4 for upper left. */ _quadrant = Biltong.quadrant = function(p1, p2) { return _pointHelper(p1, p2, function(_p1, _p2) { if (_p2[0] > _p1[0]) { return (_p2[1] > _p1[1]) ? 2 : 1; } else if (_p2[0] == _p1[0]) { return _p2[1] > _p1[1] ? 2 : 1; } else { return (_p2[1] > _p1[1]) ? 3 : 4; } }); }, /** * @name Biltong.theta * @function * @desc Calculates the angle between the two points. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Float} The angle between the two points. */ _theta = Biltong.theta = function(p1, p2) { return _pointHelper(p1, p2, function(_p1, _p2) { var m = _gradient(_p1, _p2), t = Math.atan(m), s = _quadrant(_p1, _p2); if ((s == 4 || s== 3)) t += Math.PI; if (t < 0) t += (2 * Math.PI); return t; }); }, /** * @name Biltong.intersects * @function * @desc Calculates whether or not the two rectangles intersect. * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` * @return {Boolean} True if the rectangles intersect, false otherwise. */ _intersects = Biltong.intersects = function(r1, r2) { var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h; return ( (x1 <= a1 && a1 <= x2) && (y1 <= b1 && b1 <= y2) ) || ( (x1 <= a2 && a2 <= x2) && (y1 <= b1 && b1 <= y2) ) || ( (x1 <= a1 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || ( (x1 <= a2 && a1 <= x2) && (y1 <= b2 && b2 <= y2) ) || ( (a1 <= x1 && x1 <= a2) && (b1 <= y1 && y1 <= b2) ) || ( (a1 <= x2 && x2 <= a2) && (b1 <= y1 && y1 <= b2) ) || ( (a1 <= x1 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ) || ( (a1 <= x2 && x1 <= a2) && (b1 <= y2 && y2 <= b2) ); }, /** * @name Biltong.encloses * @function * @desc Calculates whether or not r2 is completely enclosed by r1. * @param {Rectangle} r1 First rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` * @param {Rectangle} r2 Second rectangle, as a js object in the form `{x:.., y:.., w:.., h:..}` * @param {Boolean} [allowSharedEdges=false] If true, the concept of enclosure allows for one or more edges to be shared by the two rectangles. * @return {Boolean} True if r1 encloses r2, false otherwise. */ _encloses = Biltong.encloses = function(r1, r2, allowSharedEdges) { var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h, a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h, c = function(v1, v2, v3, v4) { return allowSharedEdges ? v1 <= v2 && v3>= v4 : v1 < v2 && v3 > v4; }; return c(x1,a1,x2,a2) && c(y1,b1,y2,b2); }, _segmentMultipliers = [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ], _inverseSegmentMultipliers = [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ], /** * @name Biltong.pointOnLine * @function * @desc Calculates a point on the line from `fromPoint` to `toPoint` that is `distance` units along the length of the line. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Point} Point on the line, in the form `{ x:..., y:... }`. */ _pointOnLine = Biltong.pointOnLine = function(fromPoint, toPoint, distance) { var m = _gradient(fromPoint, toPoint), s = _quadrant(fromPoint, toPoint), segmentMultiplier = distance > 0 ? _segmentMultipliers[s] : _inverseSegmentMultipliers[s], theta = Math.atan(m), y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1], x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0]; return { x:fromPoint.x + x, y:fromPoint.y + y }; }, /** * @name Biltong.perpendicularLineTo * @function * @desc Calculates a line of length `length` that is perpendicular to the line from `fromPoint` to `toPoint` and passes through `toPoint`. * @param {Point} p1 First point, either as a 2 entry array or object with `left` and `top` properties. * @param {Point} p2 Second point, either as a 2 entry array or object with `left` and `top` properties. * @return {Line} Perpendicular line, in the form `[ { x:..., y:... }, { x:..., y:... } ]`. */ _perpendicularLineTo = Biltong.perpendicularLineTo = function(fromPoint, toPoint, length) { var m = _gradient(fromPoint, toPoint), theta2 = Math.atan(-1 / m), y = length / 2 * Math.sin(theta2), x = length / 2 * Math.cos(theta2); return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}]; }; }).call(this);