如何测试线段是否与2D中的轴对齐矩形相交?

时间:2008-09-19 03:39:35

标签: graphics geometry 2d

如何测试线段是否与2D中的轴对齐矩形相交?该段的两端定义为:p1,p2。矩形定义为左上角和右下角。

12 个答案:

答案 0 :(得分:51)

原始海报想要检测线段和多边形之间的交叉点。如果有交叉点,则无需定位交叉点。如果这就是你的意思,那么你可以做的工作少于Liang-Barsky或Cohen-Sutherland:

令分段端点为p1 =(x1 y1),p2 =(x2 y2)。
让矩形的角为(xBL yBL)和(xTR yTR)。

然后你所要做的就是

一个。检查矩形的所有四个角是否都在线的同一侧。 通过p1和p2的线的隐式方程是:

F(x y)=(y2-y1)* x +(x1-x2)* y +(x2 * y1-x1 * y2)

如果F(x y)= 0,则(x y)在该行上。
如果F(x y)> 0,(x y)在该线的“上方”。
如果F(x y)< 0,(x y)在该行的“下方”。

将所有四个角替换为F(x y)。如果它们都是负数或全部为正数,则没有交集。如果有些是肯定的而有些是负面的,请转到步骤B.

B中。将端点投影到x轴上,并检查线段的阴影是否与多边形的阴影相交。在y轴上重复:

如果(x1> xTR和x2> xTR),则没有交点(行是矩形的右边)。
如果(x1&lt; xBL和x2&lt; xBL),则没有交点(线是左边的矩形)。
如果(y1> yTR和y2> yTR),则没有交点(线在矩形上方)。
如果(y1&lt; yBL和y2&lt; yBL),则没有交点(线在矩形下面)。
否则,有一个十字路口。 Cohen-Sutherland或您问题的其他答案中提到的任何代码。

当然,你可以先做B,然后做A。

阿莱霍

答案 1 :(得分:26)

写了一个非常简单和有效的解决方案:

      bool SegmentIntersectRectangle(double a_rectangleMinX,
                                 double a_rectangleMinY,
                                 double a_rectangleMaxX,
                                 double a_rectangleMaxY,
                                 double a_p1x,
                                 double a_p1y,
                                 double a_p2x,
                                 double a_p2y)
  {
    // Find min and max X for the segment

    double minX = a_p1x;
    double maxX = a_p2x;

    if(a_p1x > a_p2x)
    {
      minX = a_p2x;
      maxX = a_p1x;
    }

    // Find the intersection of the segment's and rectangle's x-projections

    if(maxX > a_rectangleMaxX)
    {
      maxX = a_rectangleMaxX;
    }

    if(minX < a_rectangleMinX)
    {
      minX = a_rectangleMinX;
    }

    if(minX > maxX) // If their projections do not intersect return false
    {
      return false;
    }

    // Find corresponding min and max Y for min and max X we found before

    double minY = a_p1y;
    double maxY = a_p2y;

    double dx = a_p2x - a_p1x;

    if(Math::Abs(dx) > 0.0000001)
    {
      double a = (a_p2y - a_p1y) / dx;
      double b = a_p1y - a * a_p1x;
      minY = a * minX + b;
      maxY = a * maxX + b;
    }

    if(minY > maxY)
    {
      double tmp = maxY;
      maxY = minY;
      minY = tmp;
    }

    // Find the intersection of the segment's and rectangle's y-projections

    if(maxY > a_rectangleMaxY)
    {
      maxY = a_rectangleMaxY;
    }

    if(minY < a_rectangleMinY)
    {
      minY = a_rectangleMinY;
    }

    if(minY > maxY) // If Y-projections do not intersect return false
    {
      return false;
    }

    return true;
  }

答案 2 :(得分:7)

由于矩形是对齐的,所以Liang-Barsky可能是一个很好的解决方案。它比Cohen-Sutherland快,如果速度很重要的话。

Siggraph explanation
Another good description
And of course, Wikipedia

答案 3 :(得分:7)

你也可以在片段之外创建一个矩形,并测试另一个矩形是否与它发生碰撞,因为它只是一系列的比较。来自pygame来源:

def _rect_collide(a, b):
    return a.x + a.w > b.x and b.x + b.w > a.x and \
           a.y + a.h > b.y and b.y + b.h > a.y

答案 4 :(得分:5)

使用Cohen-Sutherland algorithm

它用于剪辑,但可以稍微调整一下这项任务。它将2D空间划分为井字板,矩形为“中心正方形” 然后它检查你的线的两个点中的每个九个区域中的哪一个。

  • 如果两个点分别为左,右,上或下,则您可以轻易拒绝。
  • 如果任何一个点在里面,你就可以接受了。
  • 在极少数情况下,你可以根据它们所在的区域,与矩形的任何一边相交,进行数学交叉。

答案 5 :(得分:3)

或者只使用/复制Java方法中已有的代码

java.awt.geom.Rectangle2D.intersectsLine(double x1, double y1, double x2, double y2)

以下是为方便起见转换为静态后的方法:

/**
 * Code copied from {@link java.awt.geom.Rectangle2D#intersectsLine(double, double, double, double)}
 */
public class RectangleLineIntersectTest {
    private static final int OUT_LEFT = 1;
    private static final int OUT_TOP = 2;
    private static final int OUT_RIGHT = 4;
    private static final int OUT_BOTTOM = 8;

    private static int outcode(double pX, double pY, double rectX, double rectY, double rectWidth, double rectHeight) {
        int out = 0;
        if (rectWidth <= 0) {
            out |= OUT_LEFT | OUT_RIGHT;
        } else if (pX < rectX) {
            out |= OUT_LEFT;
        } else if (pX > rectX + rectWidth) {
            out |= OUT_RIGHT;
        }
        if (rectHeight <= 0) {
            out |= OUT_TOP | OUT_BOTTOM;
        } else if (pY < rectY) {
            out |= OUT_TOP;
        } else if (pY > rectY + rectHeight) {
            out |= OUT_BOTTOM;
        }
        return out;
    }

    public static boolean intersectsLine(double lineX1, double lineY1, double lineX2, double lineY2, double rectX, double rectY, double rectWidth, double rectHeight) {
        int out1, out2;
        if ((out2 = outcode(lineX2, lineY2, rectX, rectY, rectWidth, rectHeight)) == 0) {
            return true;
        }
        while ((out1 = outcode(lineX1, lineY1, rectX, rectY, rectWidth, rectHeight)) != 0) {
            if ((out1 & out2) != 0) {
                return false;
            }
            if ((out1 & (OUT_LEFT | OUT_RIGHT)) != 0) {
                double x = rectX;
                if ((out1 & OUT_RIGHT) != 0) {
                    x += rectWidth;
                }
                lineY1 = lineY1 + (x - lineX1) * (lineY2 - lineY1) / (lineX2 - lineX1);
                lineX1 = x;
            } else {
                double y = rectY;
                if ((out1 & OUT_BOTTOM) != 0) {
                    y += rectHeight;
                }
                lineX1 = lineX1 + (y - lineY1) * (lineX2 - lineX1) / (lineY2 - lineY1);
                lineY1 = y;
            }
        }
        return true;
    }
}

答案 6 :(得分:1)

快速Google搜索会弹出一个包含用于测试交叉点的C ++代码的页面。

基本上它会测试直线与每个边框或矩形之间的交点。

Rectangle and line intersection code

答案 7 :(得分:0)

我做了一个小餐巾解决方案..

接下来找到m和c,因此方程 y = mx + c

y = (Point2.Y - Point1.Y) / (Point2.X - Point1.X)

替换P1坐标现在找到c

现在对于一个矩形顶点,将X值放在线方程中,得到Y值,看看Y值是否位于下面显示的矩形边界内

(你可以找到矩形的常数值X1,X2,Y1,Y2)

X1 <= x <= X2 & 
Y1 <= y <= Y2

如果Y值满足上述条件且位于(Point1.Y,Point2.Y)之间 - 我们有一个交集。 如果这个顶点无法切割,请尝试每个顶点。

答案 8 :(得分:0)

我正在看一个类似的问题,这就是我想出来的。我首先比较边缘并实现了一些东西。如果落在第一个盒子的相对轴线内的边缘的中点在同一轴线上第一个盒子上的外部点的边缘的长度的一半之内,那么在某个地方存在该侧面的交叉点。 但这是在考虑尺寸,并且需要查看第二个框的每一边才能弄明白。

我突然想到,如果你找到第二个盒子的“中点”并比较中点的坐标,看看它们是否落在外部尺寸的一侧(第二个盒子的)的1/2长度内第一个,然后在某个地方有一个交叉点。

i.e. box 1 is bounded by x1,y1 to x2,y2
box 2 is bounded by a1,b1 to a2,b2

the width and height of box 2 is:
w2 = a2 - a1   (half of that is w2/2)
h2 = b2 - b1   (half of that is h2/2)
the midpoints of box 2 are:
am = a1 + w2/2
bm = b1 + h2/2

So now you just check if
(x1 - w2/2) < am < (x2 + w2/2) and (y1 - h2/2) < bm < (y2 + h2/2) 
then the two overlap somewhere.
If you want to check also for edges intersecting to count as 'overlap' then
 change the < to <=

当然你可以很容易地比较另一种方式(检查box1的中点在框2的外部尺寸的1/2长度内)

更简化 - 将中点移动半长,并且与该框的原点相同。这意味着您现在可以检查该点是否落在您的边界范围内,并通过向上和向左移动平面,下角现在是第一个框的下角。更少的数学:

(x1 - w2) < a1 < x2
&&
(y1 - h2) < b1 < y2
[overlap exists]

或未取代:

( (x1-(a2-a1)) < a1 < x2 ) && ( (y1-(b2-b1)) < b1 < y2 ) [overlap exists]
( (x1-(a2-a1)) <= a1 <= x2 ) && ( (y1-(b2-b1)) <= b1 <= y2 ) [overlap or intersect exists]

答案 9 :(得分:0)

PHP中的

编码示例(我使用的对象模型具有getLeft(),getRight(),getTop(),getBottom()等方法来获取多边形的外部坐标,并且还具有getWidth( )和getHeight() - 根据输入的参数,它将计算和缓存未知数 - 即我可以用x1,y1和... w,h或x2,y2创建一个多边形,它可以计算其他的多边形)

我使用'n'来指定要检查重叠的'new'项目($ nItem是我的多边形对象的一个​​实例) - 要再次测试的项目[这是一个bin / sort背包程序]是在由(相同)多边形对象的更多实例组成的数组。

public function checkForOverlaps(BinPack_Polygon $nItem) {
  // grab some local variables for the stuff re-used over and over in loop
  $nX = $nItem->getLeft();
  $nY = $nItem->getTop();
  $nW = $nItem->getWidth();
  $nH = $nItem->getHeight();
  // loop through the stored polygons checking for overlaps
  foreach($this->packed as $_i => $pI) {
    if(((($pI->getLeft()  - $nW) < $nX) && ($nX < $pI->getRight())) &&
       ((($pI->getTop()  - $nH) < $nY) && ($nY < $pI->getBottom()))) {
      return false;
    }
  }
  return true;
}

答案 10 :(得分:0)

我的解决方案的一些示例代码(在php中):

// returns 'true' on overlap checking against an array of similar objects in $this->packed
public function checkForOverlaps(BinPack_Polygon $nItem) {
  $nX = $nItem->getLeft();
  $nY = $nItem->getTop();
  $nW = $nItem->getWidth();
  $nH = $nItem->getHeight();
  // loop through the stored polygons checking for overlaps
  foreach($this->packed as $_i => $pI) {
    if(((($pI->getLeft() - $nW) < $nX) && ($nX < $pI->getRight())) && ((($pI->getTop() - $nH) < $nY) && ($nY < $pI->getBottom()))) {
      return true;
    }
  }
  return false;
}

答案 11 :(得分:0)

这是@ metamal的答案的javascript版本

var isRectangleIntersectedByLine = function (
  a_rectangleMinX,
  a_rectangleMinY,
  a_rectangleMaxX,
  a_rectangleMaxY,
  a_p1x,
  a_p1y,
  a_p2x,
  a_p2y) {

  // Find min and max X for the segment
  var minX = a_p1x
  var maxX = a_p2x

  if (a_p1x > a_p2x) {
    minX = a_p2x
    maxX = a_p1x
  }

  // Find the intersection of the segment's and rectangle's x-projections
  if (maxX > a_rectangleMaxX)
    maxX = a_rectangleMaxX

  if (minX < a_rectangleMinX)
    minX = a_rectangleMinX

  // If their projections do not intersect return false
  if (minX > maxX)
    return false

  // Find corresponding min and max Y for min and max X we found before
  var minY = a_p1y
  var maxY = a_p2y

  var dx = a_p2x - a_p1x

  if (Math.abs(dx) > 0.0000001) {
    var a = (a_p2y - a_p1y) / dx
    var b = a_p1y - a * a_p1x
    minY = a * minX + b
    maxY = a * maxX + b
  }

  if (minY > maxY) {
    var tmp = maxY
    maxY = minY
    minY = tmp
  }

  // Find the intersection of the segment's and rectangle's y-projections
  if(maxY > a_rectangleMaxY)
    maxY = a_rectangleMaxY

  if (minY < a_rectangleMinY)
    minY = a_rectangleMinY

  // If Y-projections do not intersect return false
  if(minY > maxY)
    return false

  return true
}