如何找到直线和矩形的交点?

发布于 2024-08-08 01:50:25 字数 110 浏览 4 评论 0 原文

我有一条从 A 点到 B 点的线;我有 (x,y) 两个点。我还有一个以 B 为中心的矩形以及矩形的宽度和高度。

我需要找到与矩形相交的直线上的点。有没有一个公式可以给出该点的 (x,y)?

I have a line that goes from points A to B; I have (x,y) of both points. I also have a rectangle that's centered at B and the width and height of the rectangle.

I need to find the point in the line that intersects the rectangle. Is there a formula that gives me the (x,y) of that point?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

一直在等你来 2024-08-15 01:50:25
/**
 * 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>

/**
 * 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>

小红帽 2024-08-15 01:50:25

A点总是在矩形的外部,B点总是在矩形的中心

假设矩形是轴对齐的,这使得事情变得非常简单:

直线的斜率是 s = (Ay - By)/(斧头 - Bx)。

  • 如果 -h/2 <= s * w/2 <= h/2 则直线相交:
    • 如果 Ax > 则为右边缘乙x
    • 如果 Ax < 则为左边缘Bx。
  • 如果 -w/2 <= (h/2)/s <= w/2 则直线相交:
    • 如果 Ay > 则为顶部边缘通过
    • 如果 Ay < 则为底部边缘作者:

一旦知道了它相交的边,您就知道了一个坐标:x = Bx ± w/2 或 y = By ± h/2,具体取决于您击中的边。另一个坐标由 y = By + s * w/2 或 x = Bx + (h/2)/s 给出。

The point A is always outside of the rectangle and the point B is always at the center of the rectangle

Assuming the rectangle is axis-aligned, this makes things pretty simple:

The slope of the line is s = (Ay - By)/(Ax - Bx).

  • If -h/2 <= s * w/2 <= h/2 then the line intersects:
    • The right edge if Ax > Bx
    • The left edge if Ax < Bx.
  • If -w/2 <= (h/2)/s <= w/2 then the line intersects:
    • The top edge if Ay > By
    • The bottom edge if Ay < By.

Once you know the edge it intersects you know one coordinate: x = Bx ± w/2 or y = By ± h/2 depending on which edge you hit. The other coordinate is given by y = By + s * w/2 or x = Bx + (h/2)/s.

烟雨扶苏 2024-08-15 01:50:25

您可能想查看 Graphics Gems - 这是一套经典的套装图形例程并包括许多所需的算法。尽管它是用 C 语言编写的并且有点过时,但算法仍然很出色,并且转移到其他语言应该很简单。

对于您当前的问题,只需为矩形创建四条线,然后查看哪条线与给定线相交。

You might want to check out Graphics Gems - this is a classic set of routines for graphics and includes many of the algorithms required. Although it's in C and slightly dated the algorithms still sparkle and it should be trivial to transfer to other languages.

For your current problem the just create the four lines for the rectangle and see which intersect your given line.

羞稚 2024-08-15 01:50:25

这是 Java 中的一个解决方案,如果线段(前 4 个参数)与轴对齐矩形(最后 4 个参数)相交,则返回 true。返回交点而不是布尔值是很简单的。它的工作原理是首先检查是否完全在外部,否则使用直线方程y=m*x+b。我们知道构成矩形的线是轴对齐的,因此检查很容易。

public boolean aabbContainsSegment (float x1, float y1, float x2, float y2, float minX, float minY, float maxX, float maxY) {  
    // Completely outside.
    if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
        return false;

    float m = (y2 - y1) / (x2 - x1);

    float y = m * (minX - x1) + y1;
    if (y > minY && y < maxY) return true;

    y = m * (maxX - x1) + y1;
    if (y > minY && y < maxY) return true;

    float x = (minY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    x = (maxY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    return false;
}

如果线段的起点或终点位于矩形内部,则可以采用快捷方式,但可能最好只进行数学计算,如果线段的一个或两个终点都在内部,则始终返回 true。如果您仍然需要快捷方式,请在“完全外部”检查后插入下面的代码。

// Start or end inside.
if ((x1 > minX && x1 < maxX && y1 > minY && y1 < maxY) || (x2 > minX && x2 < maxX && y2 > minY && y2 < maxY)) return true;

Here is a solution in Java that returns true if a line segment (the first 4 parameters) intersects an axis aligned rectangle (the last 4 parameters). It would be trivial to return the intersection point instead of a boolean. It works by first checking if completely outside, else using the line equation y=m*x+b. We know the lines that make up the rectangle are axis aligned, so the checks are easy.

public boolean aabbContainsSegment (float x1, float y1, float x2, float y2, float minX, float minY, float maxX, float maxY) {  
    // Completely outside.
    if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
        return false;

    float m = (y2 - y1) / (x2 - x1);

    float y = m * (minX - x1) + y1;
    if (y > minY && y < maxY) return true;

    y = m * (maxX - x1) + y1;
    if (y > minY && y < maxY) return true;

    float x = (minY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    x = (maxY - y1) / m + x1;
    if (x > minX && x < maxX) return true;

    return false;
}

It is possible to shortcut if the start or end of the segment is inside the rectangle, but probably it is better to just do the math, which will always return true if either or both segment ends are inside. If you want the shortcut anyway, insert the code below after the "completely outside" check.

// Start or end inside.
if ((x1 > minX && x1 < maxX && y1 > minY && y1 < maxY) || (x2 > minX && x2 < maxX && y2 > minY && y2 < maxY)) return true;
ζ澈沫 2024-08-15 01:50:25

鉴于最初的问题,我认为@ivanross 的答案是迄今为止最简洁和清晰的,而且我发现自己使用了相同的方法。

输入图像描述这里

矩形,

  • 如果我们有一个以 B 为中心
  • 、边平行于 x 轴和 y 轴的

我们可以使用一些三角函数来得到:

  • tan φ (phi) = h/w
  • tan θ (theta) = (yB -yA)/(xB-xA)

和一些简单的数学运算来获取点 A 位于哪个象限(以 B 为中心的 xy 平面)。

最后,我们再次应用基本的三角学原理,比较角度并使用切线计算交点的坐标。

/**
 * Finds the intersection point between
 *     * a rectangle centered in point B
 *       with sides parallel to the x and y axes
 *     * a line passing through points A and B (the center of the rectangle)
 *
 * @param width: rectangle width
 * @param height: rectangle height
 * @param xB; rectangle center x coordinate
 * @param yB; rectangle center y coordinate
 * @param xA; point A x coordinate
 * @param yA; point A y coordinate
 * @author Federico Destefanis
 * @see <a href="https://stackoverflow.com/a/31254199/2668213">based on</a>
 */

function lineIntersectionOnRect(width, height, xB, yB, xA, yA) {

  var w = width / 2;
  var h = height / 2;

  var dx = xA - xB;
  var dy = yA - yB;

  //if A=B return B itself
  if (dx == 0 && dy == 0) return {
    x: xB,
    y: yB
  };

  var tan_phi = h / w;
  var tan_theta = Math.abs(dy / dx);

  //tell me in which quadrant the A point is
  var qx = Math.sign(dx);
  var qy = Math.sign(dy);


  if (tan_theta > tan_phi) {
    xI = xB + (h / tan_theta) * qx;
    yI = yB + h * qy;
  } else {
    xI = xB + w * qx;
    yI = yB + w * tan_theta * qy;
  }

  return {
    x: xI,
    y: yI
  };

}


var coords = lineIntersectionOnRect(6, 4, 0, 0, 1, 0);
console.log(coords);

Given the original question, I think that @ivanross answer is the most concise and clear so far, and I found myself using the same approach.

enter image description here

If we have a rectangle

  • centered in B
  • with sides parallel to x and y axes

we can use a bit of trigonometry to get:

  • tan φ (phi) = h/w
  • tan θ (theta) = (yB-yA)/(xB-xA)

and some trivial math to get in which quadrant (of the x-y plane centered in B) the point A is.

finally we compare the angles and use the tangents to calculate the coordinates of the intersection point, applying again basic trigonometry principles.

/**
 * Finds the intersection point between
 *     * a rectangle centered in point B
 *       with sides parallel to the x and y axes
 *     * a line passing through points A and B (the center of the rectangle)
 *
 * @param width: rectangle width
 * @param height: rectangle height
 * @param xB; rectangle center x coordinate
 * @param yB; rectangle center y coordinate
 * @param xA; point A x coordinate
 * @param yA; point A y coordinate
 * @author Federico Destefanis
 * @see <a href="https://stackoverflow.com/a/31254199/2668213">based on</a>
 */

function lineIntersectionOnRect(width, height, xB, yB, xA, yA) {

  var w = width / 2;
  var h = height / 2;

  var dx = xA - xB;
  var dy = yA - yB;

  //if A=B return B itself
  if (dx == 0 && dy == 0) return {
    x: xB,
    y: yB
  };

  var tan_phi = h / w;
  var tan_theta = Math.abs(dy / dx);

  //tell me in which quadrant the A point is
  var qx = Math.sign(dx);
  var qy = Math.sign(dy);


  if (tan_theta > tan_phi) {
    xI = xB + (h / tan_theta) * qx;
    yI = yB + h * qy;
  } else {
    xI = xB + w * qx;
    yI = yB + w * tan_theta * qy;
  }

  return {
    x: xI,
    y: yI
  };

}


var coords = lineIntersectionOnRect(6, 4, 0, 0, 1, 0);
console.log(coords);

美人如玉 2024-08-15 01:50:25

这是一个适合我的解决方案。我假设矩形与轴对齐。

数据:

// Center of the Rectangle
let Cx: number
let Cy: number
// Width
let w: number
// Height
let h: number

// Other Point
let Ax: number
let Ay: number

现在将点 A 平移到矩形的中心,使矩形以 O(0,0) 为中心,并考虑第一季度的问题(即 x > 0 且 y > 0)。

// Coordinates Translated
let Px = Math.abs(Ax - Cx)
let Py = Math.abs(Ay - Cy)

// Slope of line from Point P to Center
let Pm = Py / Px

// Slope of rectangle Diagonal
let Rm = h / w

// If the point is inside the rectangle, return the center
let res: [number, number] = [0, 0]

// Check if the point is inside and if so do not calculate
if (!(Px < w / 2 && Py < h / 2)) {

    // Calculate point in first quarter: Px >= 0 && Py >= 0
    if (Pm <= Rm) {
        res[0] = w / 2
        res[1] = (w * Pm) / 2
    } else {
        res[0] = h / (Pm * 2)
        res[1] = h / 2
    }

    // Set original sign 
    if (Ax - Cx < 0) res[0] *= -1
    if (Ay - Cy < 0) res[1] *= -1
}

// Translate back
return [res[0] + Cx, res[1] + Cy]

Here is a solution that works for me. I assume that the rect is aligned to the axes.

Data:

// Center of the Rectangle
let Cx: number
let Cy: number
// Width
let w: number
// Height
let h: number

// Other Point
let Ax: number
let Ay: number

Now translate point A by the center of the rectangle so the rect is centered in O(0,0) and consider the problem in the first quarter (i.e. x > 0 and y > 0).

// Coordinates Translated
let Px = Math.abs(Ax - Cx)
let Py = Math.abs(Ay - Cy)

// Slope of line from Point P to Center
let Pm = Py / Px

// Slope of rectangle Diagonal
let Rm = h / w

// If the point is inside the rectangle, return the center
let res: [number, number] = [0, 0]

// Check if the point is inside and if so do not calculate
if (!(Px < w / 2 && Py < h / 2)) {

    // Calculate point in first quarter: Px >= 0 && Py >= 0
    if (Pm <= Rm) {
        res[0] = w / 2
        res[1] = (w * Pm) / 2
    } else {
        res[0] = h / (Pm * 2)
        res[1] = h / 2
    }

    // Set original sign 
    if (Ax - Cx < 0) res[0] *= -1
    if (Ay - Cy < 0) res[1] *= -1
}

// Translate back
return [res[0] + Cx, res[1] + Cy]
半岛未凉 2024-08-15 01:50:25

我不是数学迷,也不是特别喜欢翻译其他语言的内容(如果其他人已经这样做了),所以每当我完成一项无聊的翻译任务时,我都会将其添加到引导我找到代码的文章中。防止有人做双重工作。

因此,如果您想在 C# 中使用此交集代码,请查看此处 http://dotnetbyexample.blogspot.nl/2013/09/utility-classes-to-check-if-lines-andor.html

I am not a math fan nor do I particularly enjoy translating stuff from other languages if others have already done so, so whenever I complete a boring translation task, I add it to the article that led me to the code. To prevent anyone doing double work.

So if you want to have this intersection code in C#, have a look here http://dotnetbyexample.blogspot.nl/2013/09/utility-classes-to-check-if-lines-andor.html

几味少女 2024-08-15 01:50:25

让我们做一些假设:

给出了点AC,这样它们就定义了一个与传统轴对齐的矩形ABCD。假设A是左下角,C是右上角( xA < xCyA < yC)。

假设XY是给定的两个点,使得X位于矩形内部(即< code>xA < xX < xC && yA < yX < yC) 且 Y 位于外部 xA < xY < xC & yA < yY < yC)。

这允许我们在段 之间定义一个唯一交点E。 >[X,Y] 和矩形 ∂ABCD

< img src="https://i.sstatic.net/ukWNO.png" alt="Illustration">

诀窍是寻找某个 0 < t < 1使得t*Y+(1-t)*X在矩形∂ABCD上通过重写条件Γ(t) ∈ ABCD。代码>为:

(xY - xX) * t ∈ [xA - xX, xC - xX](yY - yX) * t ∈ [yA - yX, yC - yX]代码>,

现在可以展开所有场景。这产生:

var t = 0;

if(xY == xX) {
    t =  max((yA - yX)/(yY - yX), (yC - yX)/(yY - yX));
} else {
    if(yY == yX) {
        t = max((xA - xX)/(xY - xX), (xC - xX)/(xY - xX));
    } else {
        if(xY > xX) {
            if(yY > yX) {
                t = min((xC - xX)/(xY - xX), (yC - yX)/(yY - yX));
            } else {
                t = min((xC - xX)/(xY - xX), (yA - yX)/(yY - yX));
            }
        } else {
            if(yY > yX) {
                t = min((xA - xX)/(xY - xX), (yC - yX)/(yY - yX));
            } else {
                t = min((xA - xX)/(xY - xX), (yA - yX)/(yY - yX));
            }
        }
    }
}

xE = t * xY + (1 - t) * xX;
yE = t * yY + (1 - t) * yX;

Let's make some assumptions :

Points A and C are given, such that they define a rectangle ABCD aligned with the traditional axes. Assume that A is the bottom-left corner, and C is the top-right (i.e. xA < xC and yA < yC).

Assume that X and Y are two points given such that X lies inside the rectangle (ie xA < xX < xC && yA < yX < yC) and Y lies outside (i.e. not(xA < xY < xC && yA < yY < yC).

This allows us to define a unique intersection point E between the segment [X,Y] and the rectangle ∂ABCD.

Illustration

The trick is to look for a certain 0 < t < 1 such that t*Y+(1-t)*X is on the rectangle ∂ABCD. By re-writing the condition Γ(t) ∈ ABCD as :

(xY - xX) * t ∈ [xA - xX, xC - xX] and (yY - yX) * t ∈ [yA - yX, yC - yX],

it is now possible to unwind all the scenarios. This yields :

var t = 0;

if(xY == xX) {
    t =  max((yA - yX)/(yY - yX), (yC - yX)/(yY - yX));
} else {
    if(yY == yX) {
        t = max((xA - xX)/(xY - xX), (xC - xX)/(xY - xX));
    } else {
        if(xY > xX) {
            if(yY > yX) {
                t = min((xC - xX)/(xY - xX), (yC - yX)/(yY - yX));
            } else {
                t = min((xC - xX)/(xY - xX), (yA - yX)/(yY - yX));
            }
        } else {
            if(yY > yX) {
                t = min((xA - xX)/(xY - xX), (yC - yX)/(yY - yX));
            } else {
                t = min((xA - xX)/(xY - xX), (yA - yX)/(yY - yX));
            }
        }
    }
}

xE = t * xY + (1 - t) * xX;
yE = t * yY + (1 - t) * yX;
空城旧梦 2024-08-15 01:50:25

我不会给你一个程序来做到这一点,但你可以这样做:

  • 计算线的角度
  • 计算从矩形中心到其一个角的线的角度
  • 根据确定的角度 直线与矩形的哪一条边相交
  • 计算矩形的边与直线的交点

I'll not give you a program to do that, but here is how you can do it:

  • calculate the angle of the line
  • calculate the angle of a line from the center of the rectangle to one of it's corners
  • based on the angles determine on which side does the line intersect the rectangle
  • calculate intersection between the side of the rectangle and the line
可是我不能没有你 2024-08-15 01:50:25

矩形中的线相交可能性

希望它 100% 有效

我也遇到了同样的问题。经过两天的努力终于我创建了这个方法,

Main方法,

    enum Line
    {
        // Inside the Rectangle so No Intersection Point(Both Entry Point and Exit Point will be Null)
        InsideTheRectangle,

        // One Point Inside the Rectangle another Point Outside the Rectangle. So it has only Entry Point
        Entry,

        // Both Point Outside the Rectangle but Intersecting. So It has both Entry and Exit Point
        EntryExit,

        // Both Point Outside the Rectangle and not Intersecting. So doesn't has both Entry and Exit Point
        NoIntersection
    }
    
    // Tuple<entryPoint, exitPoint, lineStatus>
    private Tuple<Point, Point, Line> GetIntersectionPoint(Point a, Point b, Rectangle rect)
    {
        if (IsWithinRectangle(a, rect) && IsWithinRectangle(b, rect))
        {
            // Can't set null to Point that's why I am returning just empty object
            return new Tuple<Point, Point, Line>(new Point(), new Point(), Line.InsideTheRectangle);
        }
        else if (!IsWithinRectangle(a, rect) && !IsWithinRectangle(b, rect))
        {
            if (!LineIntersectsRectangle(a, b, rect))
            {
                // Can't set null to Point that's why I am returning just empty object
                return new Tuple<Point, Point, Line>(new Point(), new Point(), Line.NoIntersection);
            }

            Point entryPoint = new Point();
            Point exitPoint = new Point();

            bool entryPointFound = false;

            // Top Line of Chart Area
            if (LineIntersectsLine(a, b, new Point(0, 0), new Point(rect.Width, 0)))
            {
                entryPoint = GetPointFromYValue(a, b, 0);
                entryPointFound = true;
            }
            // Right Line of Chart Area
            if (LineIntersectsLine(a, b, new Point(rect.Width, 0), new Point(rect.Width, rect.Height)))
            {
                if (entryPointFound)
                    exitPoint = GetPointFromXValue(a, b, rect.Width);
                else
                {
                    entryPoint = GetPointFromXValue(a, b, rect.Width);
                    entryPointFound = true;
                }
            }
            // Bottom Line of Chart
            if (LineIntersectsLine(a, b, new Point(0, rect.Height), new Point(rect.Width, rect.Height)))
            {
                if (entryPointFound)
                    exitPoint = GetPointFromYValue(a, b, rect.Height);
                else
                {
                    entryPoint = GetPointFromYValue(a, b, rect.Height);
                }
            }
            // Left Line of Chart
            if (LineIntersectsLine(a, b, new Point(0, 0), new Point(0, rect.Height)))
            {
                exitPoint = GetPointFromXValue(a, b, 0);
            }

            return new Tuple<Point, Point, Line>(entryPoint, exitPoint, Line.EntryExit);
        }
        else
        {
            Point entryPoint = GetEntryIntersectionPoint(rect, a, b);
            return new Tuple<Point, Point, Line>(entryPoint, new Point(), Line.Entry);
        }
    }

Supporting方法,

    private Point GetEntryIntersectionPoint(Rectangle rect, Point a, Point b)
    {
        // For top line of the rectangle
        if (LineIntersectsLine(new Point(0, 0), new Point(rect.Width, 0), a, b))
        {
            return GetPointFromYValue(a, b, 0);
        }
        // For right side line of the rectangle
        else if (LineIntersectsLine(new Point(rect.Width, 0), new Point(rect.Width, rect.Height), a, b))
        {
            return GetPointFromXValue(a, b, rect.Width);
        }
        // For bottom line of the rectangle
        else if (LineIntersectsLine(new Point(0, rect.Height), new Point(rect.Width, rect.Height), a, b))
        {
            return GetPointFromYValue(a, b, rect.Height);
        }
        // For left side line of the rectangle
        else
        {
            return GetPointFromXValue(a, b, 0);
        }
    }

    public bool LineIntersectsRectangle(Point p1, Point p2, Rectangle r)
    {
        return LineIntersectsLine(p1, p2, new Point(r.X, r.Y), new Point(r.X + r.Width, r.Y)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y), new Point(r.X + r.Width, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y + r.Height), new Point(r.X, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X, r.Y + r.Height), new Point(r.X, r.Y)) ||
               (r.Contains(p1) && r.Contains(p2));
    }

    private bool LineIntersectsLine(Point l1p1, Point l1p2, Point l2p1, Point l2p2)
    {
        float q = (l1p1.Y - l2p1.Y) * (l2p2.X - l2p1.X) - (l1p1.X - l2p1.X) * (l2p2.Y - l2p1.Y);
        float d = (l1p2.X - l1p1.X) * (l2p2.Y - l2p1.Y) - (l1p2.Y - l1p1.Y) * (l2p2.X - l2p1.X);

        if (d == 0)
        {
            return false;
        }

        float r = q / d;

        q = (l1p1.Y - l2p1.Y) * (l1p2.X - l1p1.X) - (l1p1.X - l2p1.X) * (l1p2.Y - l1p1.Y);
        float s = q / d;

        if (r < 0 || r > 1 || s < 0 || s > 1)
        {
            return false;
        }

        return true;
    }

    // For Large values, processing with integer is not working properly
    // So I here I am dealing only with double for high accuracy
    private Point GetPointFromYValue(Point a, Point b, double y)
    {
        double x1 = a.X, x2 = b.X, y1 = a.Y, y2 = b.Y;
        double x = (((y - y1) * (x2 - x1)) / (y2 - y1)) + x1;
        return new Point((int)x, (int)y);
    }

    // For Large values, processing with integer is not working properly
    // So here I am dealing only with double for high accuracy
    private Point GetPointFromXValue(Point a, Point b, double x)
    {
        double x1 = a.X, x2 = b.X, y1 = a.Y, y2 = b.Y;
        double y = (((x - x1) * (y2 - y1)) / (x2 - x1)) + y1;
        return new Point((int)x, (int)y);
    }

    // rect.Contains(point) is not working properly in some cases.
    // So here I created my own method
    private bool IsWithinRectangle(Point a, Rectangle rect)
    {
        return a.X >= rect.X && a.X <= rect.X + rect.Width && a.Y >= rect.Y && a.Y <= rect.Y + rect.Height;
    }

Line Intersection Possibilities in Rectangle

Hope It works 100%

I am also had this same problem. So after two days of hard effort finally I created this method,

Main method,

    enum Line
    {
        // Inside the Rectangle so No Intersection Point(Both Entry Point and Exit Point will be Null)
        InsideTheRectangle,

        // One Point Inside the Rectangle another Point Outside the Rectangle. So it has only Entry Point
        Entry,

        // Both Point Outside the Rectangle but Intersecting. So It has both Entry and Exit Point
        EntryExit,

        // Both Point Outside the Rectangle and not Intersecting. So doesn't has both Entry and Exit Point
        NoIntersection
    }
    
    // Tuple<entryPoint, exitPoint, lineStatus>
    private Tuple<Point, Point, Line> GetIntersectionPoint(Point a, Point b, Rectangle rect)
    {
        if (IsWithinRectangle(a, rect) && IsWithinRectangle(b, rect))
        {
            // Can't set null to Point that's why I am returning just empty object
            return new Tuple<Point, Point, Line>(new Point(), new Point(), Line.InsideTheRectangle);
        }
        else if (!IsWithinRectangle(a, rect) && !IsWithinRectangle(b, rect))
        {
            if (!LineIntersectsRectangle(a, b, rect))
            {
                // Can't set null to Point that's why I am returning just empty object
                return new Tuple<Point, Point, Line>(new Point(), new Point(), Line.NoIntersection);
            }

            Point entryPoint = new Point();
            Point exitPoint = new Point();

            bool entryPointFound = false;

            // Top Line of Chart Area
            if (LineIntersectsLine(a, b, new Point(0, 0), new Point(rect.Width, 0)))
            {
                entryPoint = GetPointFromYValue(a, b, 0);
                entryPointFound = true;
            }
            // Right Line of Chart Area
            if (LineIntersectsLine(a, b, new Point(rect.Width, 0), new Point(rect.Width, rect.Height)))
            {
                if (entryPointFound)
                    exitPoint = GetPointFromXValue(a, b, rect.Width);
                else
                {
                    entryPoint = GetPointFromXValue(a, b, rect.Width);
                    entryPointFound = true;
                }
            }
            // Bottom Line of Chart
            if (LineIntersectsLine(a, b, new Point(0, rect.Height), new Point(rect.Width, rect.Height)))
            {
                if (entryPointFound)
                    exitPoint = GetPointFromYValue(a, b, rect.Height);
                else
                {
                    entryPoint = GetPointFromYValue(a, b, rect.Height);
                }
            }
            // Left Line of Chart
            if (LineIntersectsLine(a, b, new Point(0, 0), new Point(0, rect.Height)))
            {
                exitPoint = GetPointFromXValue(a, b, 0);
            }

            return new Tuple<Point, Point, Line>(entryPoint, exitPoint, Line.EntryExit);
        }
        else
        {
            Point entryPoint = GetEntryIntersectionPoint(rect, a, b);
            return new Tuple<Point, Point, Line>(entryPoint, new Point(), Line.Entry);
        }
    }

Supporting methods,

    private Point GetEntryIntersectionPoint(Rectangle rect, Point a, Point b)
    {
        // For top line of the rectangle
        if (LineIntersectsLine(new Point(0, 0), new Point(rect.Width, 0), a, b))
        {
            return GetPointFromYValue(a, b, 0);
        }
        // For right side line of the rectangle
        else if (LineIntersectsLine(new Point(rect.Width, 0), new Point(rect.Width, rect.Height), a, b))
        {
            return GetPointFromXValue(a, b, rect.Width);
        }
        // For bottom line of the rectangle
        else if (LineIntersectsLine(new Point(0, rect.Height), new Point(rect.Width, rect.Height), a, b))
        {
            return GetPointFromYValue(a, b, rect.Height);
        }
        // For left side line of the rectangle
        else
        {
            return GetPointFromXValue(a, b, 0);
        }
    }

    public bool LineIntersectsRectangle(Point p1, Point p2, Rectangle r)
    {
        return LineIntersectsLine(p1, p2, new Point(r.X, r.Y), new Point(r.X + r.Width, r.Y)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y), new Point(r.X + r.Width, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X + r.Width, r.Y + r.Height), new Point(r.X, r.Y + r.Height)) ||
               LineIntersectsLine(p1, p2, new Point(r.X, r.Y + r.Height), new Point(r.X, r.Y)) ||
               (r.Contains(p1) && r.Contains(p2));
    }

    private bool LineIntersectsLine(Point l1p1, Point l1p2, Point l2p1, Point l2p2)
    {
        float q = (l1p1.Y - l2p1.Y) * (l2p2.X - l2p1.X) - (l1p1.X - l2p1.X) * (l2p2.Y - l2p1.Y);
        float d = (l1p2.X - l1p1.X) * (l2p2.Y - l2p1.Y) - (l1p2.Y - l1p1.Y) * (l2p2.X - l2p1.X);

        if (d == 0)
        {
            return false;
        }

        float r = q / d;

        q = (l1p1.Y - l2p1.Y) * (l1p2.X - l1p1.X) - (l1p1.X - l2p1.X) * (l1p2.Y - l1p1.Y);
        float s = q / d;

        if (r < 0 || r > 1 || s < 0 || s > 1)
        {
            return false;
        }

        return true;
    }

    // For Large values, processing with integer is not working properly
    // So I here I am dealing only with double for high accuracy
    private Point GetPointFromYValue(Point a, Point b, double y)
    {
        double x1 = a.X, x2 = b.X, y1 = a.Y, y2 = b.Y;
        double x = (((y - y1) * (x2 - x1)) / (y2 - y1)) + x1;
        return new Point((int)x, (int)y);
    }

    // For Large values, processing with integer is not working properly
    // So here I am dealing only with double for high accuracy
    private Point GetPointFromXValue(Point a, Point b, double x)
    {
        double x1 = a.X, x2 = b.X, y1 = a.Y, y2 = b.Y;
        double y = (((x - x1) * (y2 - y1)) / (x2 - x1)) + y1;
        return new Point((int)x, (int)y);
    }

    // rect.Contains(point) is not working properly in some cases.
    // So here I created my own method
    private bool IsWithinRectangle(Point a, Rectangle rect)
    {
        return a.X >= rect.X && a.X <= rect.X + rect.Width && a.Y >= rect.Y && a.Y <= rect.Y + rect.Height;
    }
与往事干杯 2024-08-15 01:50:25

如果您计划使用同一矩形测试多条线,您可以考虑的另一个选择是转换坐标系以使轴与矩形的对角线对齐。然后,由于您的直线或射线从矩形的中心开始,您可以确定角度,然后您可以知道它将与哪个线段相交(即 <90deg seg 1、90deg<<180deg seg 2 等...) 。然后当然你必须变换回原始坐标系

虽然这看起来像是更多的工作,但变换矩阵及其逆可以计算一次然后重复使用。这也更容易扩展到更高维度的矩形,您必须考虑 3D 中的象限和与面的交集等。

Another option that you can consider especially if you are planning on testing many lines with the same rectangle is to transform your coordinate system to have the axes align with diagonals of the rectangle. Then since your line or ray starts at the center of the rectangle you can determine the angle then you can tell which segment it will intersect by the angle (i.e. <90deg seg 1, 90deg< <180deg seg 2 etc...). Then of course you have to transform back to the original coordinate system

Although this seems like more work the transformation matrix and its inverse can be calculated once and then reused. This also extends to higher dimensional rectangles more easily where you would have to consider quadrants and intersections with faces in 3D and so on.

初心 2024-08-15 01:50:25

我不知道这是否是最好的方法,但你可以做的是找出矩形内的线的比例。您可以从矩形的宽度以及 A 和 B 的 x 坐标(或高度和 y 坐标)之间的差值得到该值;根据宽度和高度,您可以检查哪种情况适用,其他情况将在扩展上矩形的一条边)。当你有了这个,只需取从 B 到 A 的向量的比例,你就得到了交点的坐标。

I don't know if this is the best way, but what you could do is to figure out the proportion of the line that is inside the rectangle. You can get that from the width of the rectangle and the difference between the x coordinates of A and B (or height and y coordinates; based on the width and height you can check which case applies, and the other case will be on the extension of a side of the rectangle). When you have this, just take that proportion of the vector from B to A and you have your intersection point's coordinates.

三人与歌 2024-08-15 01:50:25

这是一个稍微冗长的方法,它仅使用基本数学来返回(无限)线和矩形之间的相交间隔:

// Line2      - 2D line with origin (= offset from 0,0) and direction
// Rectangle2 - 2D rectangle by min and max points
// Contacts   - Stores entry and exit times of a line through a convex shape

Contacts findContacts(const Line2 &line, const Rectangle2 &rect) {
  Contacts contacts;

  // If the line is not parallel to the Y axis, find out when it will cross
  // the limits of the rectangle horizontally
  if(line.Direction.X != 0.0f) {
    float leftTouch = (rect.Min.X - line.Origin.X) / line.Direction.X;
    float rightTouch = (rect.Max.X - line.Origin.X) / line.Direction.X;
    contacts.Entry = std::fmin(leftTouch, rightTouch);
    contacts.Exit = std::fmax(leftTouch, rightTouch);
  } else if((line.Offset.X < rect.Min.X) || (line.Offset.X >= rect.Max.X)) {
    return Contacts::None; // Rectangle missed by vertical line
  }

  // If the line is not parallel to the X axis, find out when it will cross
  // the limits of the rectangle vertically
  if(line.Direction.Y != 0.0f) {
    float topTouch = (rectangle.Min.Y - line.Offset.Y) / line.Direction.Y;
    float bottomTouch = (rectangle.Max.Y - line.Offset.Y) / line.Direction.Y;

    // If the line is parallel to the Y axis (and it goes through
    // the rectangle), only the Y axis needs to be taken into account.
    if(line.Direction.X == 0.0f) {
      contacts.Entry = std::fmin(topTouch, bottomTouch);
      contacts.Exit = std::fmax(topTouch, bottomTouch);
    } else {
      float verticalEntry = std::fmin(topTouch, bottomTouch);
      float verticalExit = std::fmax(topTouch, bottomTouch);

      // If the line already left the rectangle on one axis before entering it
      // on the other, it has missed the rectangle.
      if((verticalExit < contacts.Entry) || (contacts.Exit < verticalEntry)) {
        return Contacts::None;
      }

      // Restrict the intervals from the X axis of the rectangle to where
      // the line is also within the limits of the rectangle on the Y axis
      contacts.Entry = std::fmax(verticalEntry, contacts.Entry);
      contacts.Exit = std::fmin(verticalExit, contacts.Exit);
    }
  } else if((line.Offset.Y < rect.Min.Y) || (line.Offset.Y > rect.Max.Y)) {
    return Contacts::None; // Rectangle missed by horizontal line
  }

  return contacts;
}

这种方法提供了高度的数值稳定性(在所有情况下,间隔都是单次减法和除法)但涉及一些分支。

对于线段(具有起点和终点),您需要提供线段的起点作为原点,并提供方向end - start。计算两个交叉点的坐标非常简单,如entryPoint = origin + Direction * contacts.EntryexitPoint = origin + Direction *contacts.Exit

Here is a slightly verbose method that returns the intersection intervals between an (infinite) line and a rectangle using only basic math:

// Line2      - 2D line with origin (= offset from 0,0) and direction
// Rectangle2 - 2D rectangle by min and max points
// Contacts   - Stores entry and exit times of a line through a convex shape

Contacts findContacts(const Line2 &line, const Rectangle2 &rect) {
  Contacts contacts;

  // If the line is not parallel to the Y axis, find out when it will cross
  // the limits of the rectangle horizontally
  if(line.Direction.X != 0.0f) {
    float leftTouch = (rect.Min.X - line.Origin.X) / line.Direction.X;
    float rightTouch = (rect.Max.X - line.Origin.X) / line.Direction.X;
    contacts.Entry = std::fmin(leftTouch, rightTouch);
    contacts.Exit = std::fmax(leftTouch, rightTouch);
  } else if((line.Offset.X < rect.Min.X) || (line.Offset.X >= rect.Max.X)) {
    return Contacts::None; // Rectangle missed by vertical line
  }

  // If the line is not parallel to the X axis, find out when it will cross
  // the limits of the rectangle vertically
  if(line.Direction.Y != 0.0f) {
    float topTouch = (rectangle.Min.Y - line.Offset.Y) / line.Direction.Y;
    float bottomTouch = (rectangle.Max.Y - line.Offset.Y) / line.Direction.Y;

    // If the line is parallel to the Y axis (and it goes through
    // the rectangle), only the Y axis needs to be taken into account.
    if(line.Direction.X == 0.0f) {
      contacts.Entry = std::fmin(topTouch, bottomTouch);
      contacts.Exit = std::fmax(topTouch, bottomTouch);
    } else {
      float verticalEntry = std::fmin(topTouch, bottomTouch);
      float verticalExit = std::fmax(topTouch, bottomTouch);

      // If the line already left the rectangle on one axis before entering it
      // on the other, it has missed the rectangle.
      if((verticalExit < contacts.Entry) || (contacts.Exit < verticalEntry)) {
        return Contacts::None;
      }

      // Restrict the intervals from the X axis of the rectangle to where
      // the line is also within the limits of the rectangle on the Y axis
      contacts.Entry = std::fmax(verticalEntry, contacts.Entry);
      contacts.Exit = std::fmin(verticalExit, contacts.Exit);
    }
  } else if((line.Offset.Y < rect.Min.Y) || (line.Offset.Y > rect.Max.Y)) {
    return Contacts::None; // Rectangle missed by horizontal line
  }

  return contacts;
}

This approach offers a high degree of numerical stability (the intervals are, in all cases, the result of a single subtraction and division) but involves some branching.

For a line segment (with start and end points), you'd need to provide the segment's start point as the origin and for the direction, end - start. Calculating the coordinates of the two intersections is a simple as entryPoint = origin + direction * contacts.Entry and exitPoint = origin + direction * contacts.Exit.

戏蝶舞 2024-08-15 01:50:25

只需一点数学知识,您就可以比大多数答案所建议的更容易地解决这个问题。

策略:

  1. 变换直线和矩形,使矩形的中心位于原点(0,0)。
  2. 按矩形的宽度/2 和高度/2 缩放直线和矩形(在下面的代码示例中称为 xradius 和 yradius),因此问题本质上变成“找到 [点 p 和原点之间的一条线] 的交点[一个以原点为中心、大小为 2 的正方形]”。
  3. 现在,我们可以通过缩小点 p 的坐标,使其位于正方形上(通过使任一坐标等于 +1 或 -1)来找到正方形最近一侧的交点。
  4. 由于问题现在是对称的,如果我们取 x 和 y 的绝对值,我们可以将问题转换(镜像)到网格的“象限 1”,其中 x 和 y 都是正值。这避免了必须单独处理盒子的每一侧。
  5. 在象限 1 中找到交点。如果 p 位于 x==y 线上方,我们需要除以 y 才能将该点放在最近的线上,如果 p 位于该线下方,我们需要除以 x。请注意,即使缩放因子是使用 x 和 y 坐标的绝对值计算的,它也可以用于缩放原始坐标。
  6. 现在我们有了交点,对其进行逆变换以撤消步骤 2 和步骤 1 中的转换。

在 C++ 中,这看起来像:

struct point { double x, y; };
struct rect { point center; double xradius, yradius; }
point intersect(const point& p, const rect& r) {
    // Steps 1 and 2: Transform to origin and scale by xradius and yradius. 
    point q{ (p.x - r.center.x) / r.xradius, (p.y - r.center.y) / r.yradius };
    // Steps 3 and 4: Transform to Quadrant 1 and find scaling factor.
    double f = max(abs(q.x), abs(q.y));
    // Step 5: Divide by scaling factor to find intersection point on square.
    point intersect{ q.x/f, q.y/f };
    // Step 6: Inverse transformations from steps 1 and 2.
    return {intersect.x * r.xradius + r.center.x, intersect.y * r.yradius + r.center.y};
}

如果您可以通过运算符重载访问真正的向量和矩阵类,则代码变得均匀更简单:

point intersect(const point& p, const rect& r) {
    matrix m = scale_matrix(r.xradius, r.yradius)*translate_matrix(p - r.center)
    point q = m * p;
    return m.inverse() * q/(max(abs(q.x), abs(q.y)))
}

警告:此代码不处理 px==r.centerx && 的情况py==r.centery 这将导致步骤 5 中出现被零除错误。处理该错误情况将留给读者作为练习。 :-)

With a little math, you can solve this problem in a much easier fashion than most of these answers suggest.

Strategy:

  1. Transform the line and rectangle so that the center of the rectangle is at the origin (0,0).
  2. Scale both line and rectangle by the width/2 and height/2 of the rectangle (called xradius and yradius in my code sample below), so the problem becomes essentially "find the intersection point of [a line between point p and the origin] with [a square of size 2 which is centered on the origin]".
  3. We can now find the intersection point on the nearest side of the square by scaling down the coordinates of point p so that it lies on the square (by making either of the coordinates equal to +1 or -1).
  4. Since the problem is now symmetrical, if we take the absolute value of x and y, we can transform (mirror) the problem into only "Quadrant 1" of the grid, where x and y are both positive. This avoids having to handle each side of the box individually.
  5. In Quadrant 1, find the intersection point. If p is above the line x==y, we need to divide by y to put the point on the nearest line, and if it's below that line we need to divide by x instead. Note that even though the scaling factor was calculated with the absolute values of the x and y coordinates, it can be used to scale the original coordinates also.
  6. Now that we have the intersection point, inverse-transform it to undo the transformations from step 2 and step 1.

In C++, this looks something like:

struct point { double x, y; };
struct rect { point center; double xradius, yradius; }
point intersect(const point& p, const rect& r) {
    // Steps 1 and 2: Transform to origin and scale by xradius and yradius. 
    point q{ (p.x - r.center.x) / r.xradius, (p.y - r.center.y) / r.yradius };
    // Steps 3 and 4: Transform to Quadrant 1 and find scaling factor.
    double f = max(abs(q.x), abs(q.y));
    // Step 5: Divide by scaling factor to find intersection point on square.
    point intersect{ q.x/f, q.y/f };
    // Step 6: Inverse transformations from steps 1 and 2.
    return {intersect.x * r.xradius + r.center.x, intersect.y * r.yradius + r.center.y};
}

if you have access to a true vector and matrix class with operator overloading, the code becomes even easier:

point intersect(const point& p, const rect& r) {
    matrix m = scale_matrix(r.xradius, r.yradius)*translate_matrix(p - r.center)
    point q = m * p;
    return m.inverse() * q/(max(abs(q.x), abs(q.y)))
}

Warning: This code doesn't handle the case where p.x==r.centerx && p.y==r.centery which will result in a divide-by-zero error in step 5. Handling that error condition will be left as an exercise to the reader. :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文