如何找到一条直线和一个矩形之间的交点?

68

我有一条从点 A 到点 B 的直线;我知道这两个点的坐标 (x,y)。我还有一个以点 B 为中心、宽和高已知的矩形。

我需要找到这条直线与矩形相交的点的坐标 (x,y)。是否有一个公式可以给出这个点的坐标?


3
我们可以假设矩形与坐标轴对齐而且没有倾斜吗? - Grandpa
20
对于那些想要关闭此问题的人:传统上,我们允许这种数学问题作为编程问题的近似,因为它们在真实的编程和编程教育中都很常见。我会关注的是这些问题是否有可能是重复的。请注意,我只是翻译,不会添加解释或其他内容。 - dmckee --- ex-moderator kitten
14个回答

31

/**
 * 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="https://dev59.com/nXI-5IYBdhLWcg3w-9sK#31254199">source</a>
 * @see <a href="https://dev59.com/nXI-5IYBdhLWcg3w-9sK#18292964">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>


非常好的答案。我刚刚无耻地从您的函数中窃取了这个问题并且完美地运行了。 - Mark
3
@Mark:归属权声明从来都不是无耻的行为,比仅提供链接回答要好得多;) - TWiStErRob
很好,这正是我需要的 ;) - canbax

29

点A始终在矩形外部,点B始终位于矩形中心。

假设矩形轴对齐,则问题相当简单:

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

  • 如果 -h/2 <= s * w/2 <= h/2,那么直线与以下边界相交:
    • 如果 Ax > Bx,则与右边相交
    • 如果 Ax < Bx,则与左边相交。
  • 如果 -w/2 <= (h/2)/s <= w/2,那么直线与以下边界相交:
    • 如果 Ay > By,则与上边相交
    • 如果 Ay < By,则与下边相交。

一旦知道它所穿过的边界,就可以确定一个坐标:x = Bx ± w/2 或 y = By ± h/2,具体取决于它碰到哪条边。另一个坐标由 y = By + s * w/2 或 x = Bx + (h/2)/s 给出。


4
谢谢Joren,我已经制作了这个算法的示例:http://jsfiddle.net/524ctnfh/看起来左右和上下边缘被颠倒了,所以应该是: 右侧:Ax < Bx; *左侧*:Ax > Bx; 顶部:Ay < By; *底部*:Ay > By; - Johnner
3
抱歉,我在脚本中犯了一些错误,这是修正版:http://jsfiddle.net/524ctnfh/1/ - Johnner
一个类似的JavaScript实现:https://dev59.com/nXI-5IYBdhLWcg3w-9sK#31254199 - TWiStErRob
@Johnner:假设使用标准坐标系,其中x从左到右增加,则Ax < Bx绝对意味着点A在以B为中心的矩形的*左侧*(而Ax > Bx => 在右侧)。根据您的坐标系约定,上下确实可以翻转。我正在使用右手坐标系,其中y从底部向上增加(这是数学中的标准),而您可能正在考虑左手坐标系,其中y从顶部向下增加(这是图形和UI编程中的标准)。 - Joren
1
这个答案不完整。OP说他“需要找到与矩形相交的线段上的”,而不仅仅是它与矩形的哪一侧相交。 - cp.engr
显示剩余5条评论

19
你可能想要查看 《计算机图形学宝典》 - 这是一个经典的图形例程集,包括许多所需算法。虽然它是用C语言编写的且有点过时,但这些算法仍然很出色,并且应该很容易移植到其他语言中。
对于你当前的问题,只需创建矩形的四条线并查看哪些与给定的线相交即可。

4
这与OP所要求的相差太远了。 - TWiStErRob

11

以下是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;

2
非常感谢!这正是我在寻找的。我将它移植到了JavaScript,这是我用来测试的fiddle链接http://jsfiddle.net/pjnovas/fPMG5/,干杯! - pjnovas
我可以发现这里有几个潜在的除以零的情况。 - gzmask
1
@gzmask 是的,但该方法仍然似乎对所有输入返回正确的值(在Java和JavaScript中,x/0=Infinityx/Infinity=0)。请参见此处 - NateS
我添加了一个专门版本,其中包含所有“琐碎”的东西和“快捷方式”:https://dev59.com/nXI-5IYBdhLWcg3w-9sK#31254199 - TWiStErRob
1
警告:如果线条恰好穿过角落,则此函数返回false。https://jsfiddle.net/obgxhyku/ - Ti Hausmann
如果这条线是水平的或垂直的呢? - vidstige

6
这是适用于我的解决方案。我假设矩形已经对齐到坐标轴上。
数据:
// 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

鉴于原问题,我认为@ivanross的答案迄今为止是最简洁和清晰的,我发现自己也在使用同样的方法。

enter image description here

如果我们有一个矩形

  • 以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://dev59.com/nXI-5IYBdhLWcg3w-9sK#31254199">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);


1
它运行良好。交点是正确的。 - Shashi3456643

4

4

让我们做出一些假设:

已知点 AC,它们定义了一个与传统坐标轴对齐的矩形 ABCD。假设 A 是左下角,C 是右上角(即 xA < xCyA < yC)。

假设给定两个点 XY,其中 X 在矩形内部(即 xA < xX < xC && yA < yX < yC),而 Y 在矩形外部(即 not(xA < xY < xC && yA < yY < yC))。

这样就可以定义线段[X,Y]与矩形∂ABCD之间的唯一交点E

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;

(xY > xX)内部有一个我无法跟踪的错误。 - user1908746
1
@Lara,你所说的“无法跟踪”的错误是什么意思?你是指编译时出现的错误,还是指结果产生的错误?你是将代码复制/粘贴,还是将其翻译成你选择的语言?你确定你的点都在与我对问题的假设相兼容的位置上吗? - Anthony
当线条从矩形左侧或右侧交叉时,代码可以在线条上下交叉时正常工作,但不可。在这种情况下,“yE”被正确计算,但“xE”不是(它会逐渐偏离)。我无法找出原因,即无法追踪除该“if”外的错误。毫无疑问,这是我的错误。这是您算法的实现:https://pastebin.com/6xPnKMAB - user1908746

3

矩形中的线段交点可能性

希望它能百分之百地工作。

我也曾经遇到过这个问题。所以经过两天的努力终于创造出了这种方法,

主要方法,

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

3
我不会提供一个程序来完成这个任务,但是我可以告诉你如何做到:
  • 计算线的角度
  • 计算从矩形中心到其一个角落的线的角度
  • 根据角度确定线与矩形相交的哪一侧
  • 计算矩形边和线之间的交点

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接