【问题标题】:How to find the intersection point between a line and a rectangle?如何找到直线和矩形的交点?
【发布时间】:2010-12-07 19:36:25
【问题描述】:

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

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

【问题讨论】:

  • 我们可以假设矩形与轴对齐并且没有倾斜吗?
  • 致那些投票结束的人:传统上,我们允许这类数学问题足够接近编程问题,并且在现实生活中的编程和编程教育中足够普遍。我会在这个问题上寻找的东西是它是重复的真正可能性。

标签: algorithm geometry line intersection


【解决方案1】:

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

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

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

  • 如果 -h/2
  • 如果 Ax > Bx 则为右边缘
  • 如果 Ax
  • 如果 -w/2
  • 如果 Ay > By 则为上边缘
  • Ay 的底边
  • 一旦你知道它相交的边,你就会知道一个坐标:x = Bx ± w/2 或 y = By ± h/2,具体取决于你碰到的边。另一个坐标由 y = By + s * w/2 或 x = Bx + (h/2)/s 给出。

    【讨论】:

    • 谢谢 Joren,我对这个算法做了一个小技巧:jsfiddle.net/524ctnfh 似乎左右和上下边缘交换了,所以应该是:right i>:Ax:Ax > Bx; top: Ay 底部:Ay > By;
    • 对不起,我在脚本中犯了一些错误,这里是固定版本:jsfiddle.net/524ctnfh/1
    • 在 JavaScript 中类似的实现:stackoverflow.com/a/31254199/253468
    • @Johnner:假设x从左到右增加的标准坐标系,那么Ax left B(和 Ax > Bx => 到右边)。根据您的坐标系约定,上下确实可以翻转。我正在使用右手坐标系,其中 y 从下到上增加(这是数学中的标准),而您可能正在考虑左手坐标系,其中 y 从上到下增加(如图形和 UI 编程标准)。
    • 这个答案不完整。 OP 说他“需要[s] 找到与矩形相交的直线中的 point”——而不仅仅是它与矩形的哪一侧相交。
    【解决方案2】:

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

    【讨论】:

    • 优秀的答案。我只是无耻地为this question 窃取了你的功能并且像一个魅力一样工作。
    • @Mark Attribution 从来都不是无耻的,而且比仅链接的答案要好;)
    • 这很好,正是我需要的;)
    【解决方案3】:

    您可能想查看Graphics Gems - 这是一组经典的图形例程,包括许多所需的算法。尽管它是 C 语言并且稍微过时了,但算法仍然闪闪发光,并且转移到其他语言应该是微不足道的。

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

    【讨论】:

    • 这与 OP 的要求相差太远了。
    【解决方案4】:

    这是一个 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;
    

    【讨论】:

    • 非常感谢!这就是我要找的。我把它移到了javascript,这是我用来测试它的小提琴jsfiddle.net/pjnovas/fPMG5欢呼!
    • 我可以在这里发现一对电位除以零
    • @gzmask 确实如此,但该方法似乎仍然为所有输入返回正确的值(在 Java 和 JavaScript 中 x/0=Infinityx/Infinity=0)。见here
    • 我添加了一个专门的版本,其中包含所有“琐碎”的东西和“快捷方式”:stackoverflow.com/a/31254199/253468
    • 警告:如果线正好穿过拐角,则返回 false。 jsfiddle.net/obgxhyku
    【解决方案5】:

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

    数据:

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

    【讨论】:

      【解决方案6】:

      让我们做一些假设:

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

      假设XY 是给定的两个点,使得X 位于内部 矩形(即xA &lt; xX &lt; xC &amp;&amp; yA &lt; yX &lt; yC)并且Y 位于外部 not(xA &lt; xY &lt; xC &amp;&amp; yA &lt; yY &lt; yC).

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

      诀窍是寻找某个0 &lt; t &lt; 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;
      

      【讨论】:

      • (xY &gt; xX) 中出现我无法跟踪的错误
      • @Lara wdym by 和错误你“无法追踪”?您的意思是编译时出现错误,还是与产生的结果有关的错误?您是否已 c/p'ed 代码,或者您是否已翻译成您选择的语言?你确定你的观点都符合我对问题的假设吗?
      • 当直线在上下交叉时代码有效,但当直线从矩形的左侧或右侧交叉时无效。在这种情况下,yE 被正确计算,但xE 不是(它变得越来越远离)。我不知道为什么,即除了 if 之外无法追踪错误。我的错误不知何故,毫无疑问。这是我的算法实现:pastebin.com/6xPnKMAB
      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          【解决方案9】:

          如果您计划测试具有相同矩形的多条线,您可以考虑的另一个选项是转换您的坐标系以使轴与矩形的对角线对齐。然后,由于您的线或射线从矩形的中心开始,您可以确定角度,然后您可以判断它将与哪个段相交(即

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              希望它 100% 有效

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

              主要方法,

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

              支持方法,

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

              【讨论】:

                【解决方案12】:

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

                如果我们有一个矩形

                • 以 B 为中心
                • 边平行于 x 轴和 y 轴

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

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

                以及一些简单的数学运算来计算点 A 位于(以 B 为中心的 x-y 平面的)哪个象限。

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

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

                【讨论】:

                • 它工作正常。交点正确
                【解决方案13】:

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

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-07-23
                  • 1970-01-01
                  • 2014-06-09
                  • 1970-01-01
                  • 2013-03-09
                  • 2012-11-20
                  • 1970-01-01
                  相关资源
                  最近更新 更多