/**
* Finds the intersection point between
* * the rectangle
* with parallel sides to the x and y axes
* * the half-line pointing towards (x,y)
* originating from the middle of the rectangle
*
* Note: the function works given min[XY] <= max[XY],
* even though minY may not be the \"top\" of the rectangle
* because the coordinate system is flipped.
* Note: if the input is inside the rectangle,
* the line segment wouldn\'t have an intersection with the rectangle,
* but the projected half-line does.
* Warning: passing in the middle of the rectangle will return the midpoint itself
* there are infinitely many half-lines projected in all directions,
* so let\'s just shortcut to midpoint (GIGO).
*
* @param x:Number x coordinate of point to build the half-line from
* @param y:Number y coordinate of point to build the half-line from
* @param minX:Number the \"left\" side of the rectangle
* @param minY:Number the \"top\" side of the rectangle
* @param maxX:Number the \"right\" side of the rectangle
* @param maxY:Number the \"bottom\" side of the rectangle
* @param validate:boolean (optional) whether to treat point inside the rect as error
* @return an object with x and y members for the intersection
* @throws if validate == true and (x,y) is inside the rectangle
* @author TWiStErRob
* @licence Dual CC0/WTFPL/Unlicence, whatever floats your boat
* @see <a href=\"http://stackoverflow.com/a/31254199/253468\">source</a>
* @see <a href=\"http://stackoverflow.com/a/18292964/253468\">based on</a>
*/
function pointOnRect(x, y, minX, minY, maxX, maxY, validate) {
//assert minX <= maxX;
//assert minY <= maxY;
if (validate && (minX < x && x < maxX) && (minY < y && y < maxY))
throw \"Point \" + [x,y] + \"cannot be inside \"
+ \"the rectangle: \" + [minX, minY] + \" - \" + [maxX, maxY] + \".\";
var midX = (minX + maxX) / 2;
var midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
var m = (midY - y) / (midX - x);
if (x <= midX) { // check \"left\" side
var minXy = m * (minX - x) + y;
if (minY <= minXy && minXy <= maxY)
return {x: minX, y: minXy};
}
if (x >= midX) { // check \"right\" side
var maxXy = m * (maxX - x) + y;
if (minY <= maxXy && maxXy <= maxY)
return {x: maxX, y: maxXy};
}
if (y <= midY) { // check \"top\" side
var minYx = (minY - y) / m + x;
if (minX <= minYx && minYx <= maxX)
return {x: minYx, y: minY};
}
if (y >= midY) { // check \"bottom\" side
var maxYx = (maxY - y) / m + x;
if (minX <= maxYx && maxYx <= maxX)
return {x: maxYx, y: maxY};
}
// edge case when finding midpoint intersection: m = 0/0 = NaN
if (x === midX && y === midY) return {x: x, y: y};
// Should never happen :) If it does, please tell me!
throw \"Cannot find intersection for \" + [x,y]
+ \" inside rectangle \" + [minX, minY] + \" - \" + [maxX, maxY] + \".\";
}
(function tests() {
var left = 100, right = 200, top = 50, bottom = 150; // a square, really
var hMiddle = (left + right) / 2, vMiddle = (top + bottom) / 2;
function intersectTestRect(x, y) { return pointOnRect(x,y, left,top, right,bottom, true); }
function intersectTestRectNoValidation(x, y) { return pointOnRect(x,y, left,top, right,bottom, false); }
function checkTestRect(x, y) { return function() { return pointOnRect(x,y, left,top, right,bottom, true); }; }
QUnit.test(\"intersects left side\", function(assert) {
var leftOfRect = 0, closerLeftOfRect = 25;
assert.deepEqual(intersectTestRect(leftOfRect, 25), {x:left, y:75}, \"point above top\");
assert.deepEqual(intersectTestRect(closerLeftOfRect, top), {x:left, y:80}, \"point in line with top\");
assert.deepEqual(intersectTestRect(leftOfRect, 70), {x:left, y:90}, \"point above middle\");
assert.deepEqual(intersectTestRect(leftOfRect, vMiddle), {x:left, y:100}, \"point exact middle\");
assert.deepEqual(intersectTestRect(leftOfRect, 130), {x:left, y:110}, \"point below middle\");
assert.deepEqual(intersectTestRect(closerLeftOfRect, bottom), {x:left, y:120}, \"point in line with bottom\");
assert.deepEqual(intersectTestRect(leftOfRect, 175), {x:left, y:125}, \"point below bottom\");
});
QUnit.test(\"intersects right side\", function(assert) {
var rightOfRect = 300, closerRightOfRect = 250;
assert.deepEqual(intersectTestRect(rightOfRect, 25), {x:right, y:75}, \"point above top\");
assert.deepEqual(intersectTestRect(closerRightOfRect, top), {x:right, y:75}, \"point in line with top\");
assert.deepEqual(intersectTestRect(rightOfRect, 70), {x:right, y:90}, \"point above middle\");
assert.deepEqual(intersectTestRect(rightOfRect, vMiddle), {x:right, y:100}, \"point exact middle\");
assert.deepEqual(intersectTestRect(rightOfRect, 130), {x:right, y:110}, \"point below middle\");
assert.deepEqual(intersectTestRect(closerRightOfRect, bottom), {x:right, y:125}, \"point in line with bottom\");
assert.deepEqual(intersectTestRect(rightOfRect, 175), {x:right, y:125}, \"point below bottom\");
});
QUnit.test(\"intersects top side\", function(assert) {
var aboveRect = 0;
assert.deepEqual(intersectTestRect(80, aboveRect), {x:115, y:top}, \"point left of left\");
assert.deepEqual(intersectTestRect(left, aboveRect), {x:125, y:top}, \"point in line with left\");
assert.deepEqual(intersectTestRect(120, aboveRect), {x:135, y:top}, \"point left of middle\");
assert.deepEqual(intersectTestRect(hMiddle, aboveRect), {x:150, y:top}, \"point exact middle\");
assert.deepEqual(intersectTestRect(180, aboveRect), {x:165, y:top}, \"point right of middle\");
assert.deepEqual(intersectTestRect(right, aboveRect), {x:175, y:top}, \"point in line with right\");
assert.deepEqual(intersectTestRect(220, aboveRect), {x:185, y:top}, \"point right of right\");
});
QUnit.test(\"intersects bottom side\", function(assert) {
var belowRect = 200;
assert.deepEqual(intersectTestRect(80, belowRect), {x:115, y:bottom}, \"point left of left\");
assert.deepEqual(intersectTestRect(left, belowRect), {x:125, y:bottom}, \"point in line with left\");
assert.deepEqual(intersectTestRect(120, belowRect), {x:135, y:bottom}, \"point left of middle\");
assert.deepEqual(intersectTestRect(hMiddle, belowRect), {x:150, y:bottom}, \"point exact middle\");
assert.deepEqual(intersectTestRect(180, belowRect), {x:165, y:bottom}, \"point right of middle\");
assert.deepEqual(intersectTestRect(right, belowRect), {x:175, y:bottom}, \"point in line with right\");
assert.deepEqual(intersectTestRect(220, belowRect), {x:185, y:bottom}, \"point right of right\");
});
QUnit.test(\"intersects a corner\", function(assert) {
assert.deepEqual(intersectTestRect(left-50, top-50), {x:left, y:top}, \"intersection line aligned with top-left corner\");
assert.deepEqual(intersectTestRect(right+50, top-50), {x:right, y:top}, \"intersection line aligned with top-right corner\");
assert.deepEqual(intersectTestRect(left-50, bottom+50), {x:left, y:bottom}, \"intersection line aligned with bottom-left corner\");
assert.deepEqual(intersectTestRect(right+50, bottom+50), {x:right, y:bottom}, \"intersection line aligned with bottom-right corner\");
});
QUnit.test(\"on the corners\", function(assert) {
assert.deepEqual(intersectTestRect(left, top), {x:left, y:top}, \"top-left corner\");
assert.deepEqual(intersectTestRect(right, top), {x:right, y:top}, \"top-right corner\");
assert.deepEqual(intersectTestRect(right, bottom), {x:right, y:bottom}, \"bottom-right corner\");
assert.deepEqual(intersectTestRect(left, bottom), {x:left, y:bottom}, \"bottom-left corner\");
});
QUnit.test(\"on the edges\", function(assert) {
assert.deepEqual(intersectTestRect(hMiddle, top), {x:hMiddle, y:top}, \"top edge\");
assert.deepEqual(intersectTestRect(right, vMiddle), {x:right, y:vMiddle}, \"right edge\");
assert.deepEqual(intersectTestRect(hMiddle, bottom), {x:hMiddle, y:bottom}, \"bottom edge\");
assert.deepEqual(intersectTestRect(left, vMiddle), {x:left, y:vMiddle}, \"left edge\");
});
QUnit.test(\"validates inputs\", function(assert) {
assert.throws(checkTestRect(hMiddle, vMiddle), /cannot be inside/, \"center\");
assert.throws(checkTestRect(hMiddle-10, vMiddle-10), /cannot be inside/, \"top left of center\");
assert.throws(checkTestRect(hMiddle-10, vMiddle), /cannot be inside/, \"left of center\");
assert.throws(checkTestRect(hMiddle-10, vMiddle+10), /cannot be inside/, \"bottom left of center\");
assert.throws(checkTestRect(hMiddle, vMiddle-10), /cannot be inside/, \"above center\");
assert.throws(checkTestRect(hMiddle, vMiddle), /cannot be inside/, \"center\");
assert.throws(checkTestRect(hMiddle, vMiddle+10), /cannot be inside/, \"below center\");
assert.throws(checkTestRect(hMiddle+10, vMiddle-10), /cannot be inside/, \"top right of center\");
assert.throws(checkTestRect(hMiddle+10, vMiddle), /cannot be inside/, \"right of center\");
assert.throws(checkTestRect(hMiddle+10, vMiddle+10), /cannot be inside/, \"bottom right of center\");
assert.throws(checkTestRect(left+10, vMiddle-10), /cannot be inside/, \"right of left edge\");
assert.throws(checkTestRect(left+10, vMiddle), /cannot be inside/, \"right of left edge\");
assert.throws(checkTestRect(left+10, vMiddle+10), /cannot be inside/, \"right of left edge\");
assert.throws(checkTestRect(right-10, vMiddle-10), /cannot be inside/, \"left of right edge\");
assert.throws(checkTestRect(right-10, vMiddle), /cannot be inside/, \"left of right edge\");
assert.throws(checkTestRect(right-10, vMiddle+10), /cannot be inside/, \"left of right edge\");
assert.throws(checkTestRect(hMiddle-10, top+10), /cannot be inside/, \"below top edge\");
assert.throws(checkTestRect(hMiddle, top+10), /cannot be inside/, \"below top edge\");
assert.throws(checkTestRect(hMiddle+10, top+10), /cannot be inside/, \"below top edge\");
assert.throws(checkTestRect(hMiddle-10, bottom-10), /cannot be inside/, \"above bottom edge\");
assert.throws(checkTestRect(hMiddle, bottom-10), /cannot be inside/, \"above bottom edge\");
assert.throws(checkTestRect(hMiddle+10, bottom-10), /cannot be inside/, \"above bottom edge\");
});
QUnit.test(\"doesn\'t validate inputs\", function(assert) {
assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle-10), {x:left, y:top}, \"top left of center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle), {x:left, y:vMiddle}, \"left of center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle-10, vMiddle+10), {x:left, y:bottom}, \"bottom left of center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle-10), {x:hMiddle, y:top}, \"above center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle), {x:hMiddle, y:vMiddle}, \"center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle, vMiddle+10), {x:hMiddle, y:bottom}, \"below center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle-10), {x:right, y:top}, \"top right of center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle), {x:right, y:vMiddle}, \"right of center\");
assert.deepEqual(intersectTestRectNoValidation(hMiddle+10, vMiddle+10), {x:right, y:bottom}, \"bottom right of center\");
});
})();
<link href=\"https://code.jquery.com/qunit/qunit-2.3.2.css\" rel=\"stylesheet\"/>
<script src=\"https://code.jquery.com/qunit/qunit-2.3.2.js\"></script>
<div id=\"qunit\"></div>