【问题标题】:How to check if line segment intersects a rectangle?如何检查线段是否与矩形相交?
【发布时间】:2025-11-29 01:15:02
【问题描述】:

如果你有 2 个点 (x1, y1) 和 (x2, y2),它们代表一个矩形的两个对角,还有 2 个点,(x3,y3) 和 (x4,y4),它们代表 2线段的端点,如何检查线段是否与矩形相交?

(线段只是包含在给定端点之间的线段。它不是由这两个点定义的无限长线。)

【问题讨论】:

标签: algorithm language-agnostic geometry computational-geometry


【解决方案1】:

一个非常简单的选项是使用a standard algorithm for checking whether two line segments intersect 来检查线段是否与构成框角的四个线段中的任何一个相交。检查两条线段是否相交在计算上非常有效,所以我希望它可以运行得非常快。

希望这会有所帮助!

【讨论】:

  • @templatetypedef 给出的想法没有处理一种情况:线段的两个端点在矩形内的情况。但这种情况很容易检查:x1 < x3 && x3 < x2 && y1 < y3 && y3 < y2.
  • @lrineau 除非它包含在矩形中,否则它不会与矩形相交。
  • @MarkPing:这取决于您是按原样考虑矩形,还是只考虑它的边界。
  • 如果线段直接通过矩形的一个角,从而导致矩形两侧接触该角的相交检查失败,那么最好的处理方法是什么?
  • 如果线路撞到角落,碰撞检查不会通过吗?假设你对每条边都进行了包容性检查,你应该没问题。
【解决方案2】:

得到所有4个顶点(矩形的角)与线段方向向量的点积。如果所有 4 个值的符号相同,则所有顶点都位于直线的同一侧(不是线段,而是无限线),因此直线不与矩形相交。这种方法仅适用于二维交叉点检测。这可用于快速过滤其中的大多数(仅使用乘法和加法)。您必须进一步检查线段而不是线。

【讨论】:

  • 我一直在想这个……不正确。所有顶点都可以位于直线的同一侧,但仍会产生符号相反的点积。同样使用方向向量也没有考虑线的实际位置。可以选择两条平行线:一条与矩形相交,另一条不相交。由于它们的方向相同,因此四个点积将为两条线产生相同的值,这显然与定理相矛盾。我必须 -1 这个,虽然最初的想法很好。
【解决方案3】:

要了解如何推导出测试线段是否与矩形相交的公式,请务必记住 vector dot product 的属性。

将线段表示为单位向量以及线段起点与原点之间的距离。下面是一些 C# 代码,用于从 PointF 变量 a_ptStarta_ptEnd 计算,使用 Vector

Vector vecLine = new Vector(a_ptEnd.X - a_ptStart.X, a_ptEnd.Y - a_ptStart.Y);
double dLengthLine = vecLine.Length;
vecLine /= dLengthLine;
double dDistLine = Vector.Multiply(vecLine, new Vector(a_ptStart.X, a_ptStart.Y));

您还需要计算线段的垂直向量及其到原点的距离。将单位向量旋转 90° 为easy

Vector vecPerpLine = new Vector(-vecLine.Y, vecLine.X);
double dDistPerpLine = Vector.Multiply(vecPerpLine, new Vector(a_ptStart.X, a_ptStart.Y));

假设矩形的四个角位于Vector 变量中,称为vecRect1vecRect2vecRect3vecRect4,计算线段和矩形的所有四个角之间的distance目标的边界矩形:

double dPerpLineDist1 = Vector.Multiply(vecPerpLine, vecRect1) - dDistPerpLine;
double dPerpLineDist2 = Vector.Multiply(vecPerpLine, vecRect2) - dDistPerpLine;
double dPerpLineDist3 = Vector.Multiply(vecPerpLine, vecRect3) - dDistPerpLine;
double dPerpLineDist4 = Vector.Multiply(vecPerpLine, vecRect4) - dDistPerpLine;
double dMinPerpLineDist = Math.Min(dPerpLineDist1, Math.Min(dPerpLineDist2,
    Math.Min(dPerpLineDist3, dPerpLineDist4)));
double dMaxPerpLineDist = Math.Max(dPerpLineDist1, Math.Max(dPerpLineDist2,
    Math.Max(dPerpLineDist3, dPerpLineDist4)));

如果所有距离都是正数,或者所有距离都是负数,则矩形位于直线的一侧或另一侧,因此不存在交点。 (零范围的矩形被认为不与任何线段相交。)

if (dMinPerpLineDist <= 0.0 && dMaxPerpLineDist <= 0.0
        || dMinPerpLineDist >= 0.0 && dMaxPerpLineDist >= 0.0)
    /* no intersection */;

接下来,将目标边界矩形的所有四个角投影到线段上。这给了我们直线的原点和直线上矩形角的投影之间的距离。

double dDistLine1 = Vector.Multiply(vecLine, vecRect1) - dDistLine;
double dDistLine2 = Vector.Multiply(vecLine, vecRect2) - dDistLine;
double dDistLine3 = Vector.Multiply(vecLine, vecRect3) - dDistLine;
double dDistLine4 = Vector.Multiply(vecLine, vecRect4) - dDistLine;
double dMinLineDist = Math.Min(dDistLine1, Math.Min(dDistLine2,
    Math.Min(dDistLine3, dDistLine4)));
double dMaxLineDist = Math.Max(dDistLine1, Math.Max(dDistLine2,
    Math.Max(dDistLine3, dDistLine4)));

如果矩形的点不在线段的范围内,则没有交点。

if (dMaxLineDist <= 0.0 || dMinLineDist >= dLengthLine)
    /* no intersection */;

我相信这就足够了。

【讨论】:

  • 这种方法能推广到 3D 吗?假设我们有那个矩形的法线。使用线段方向和法线,我们可以得到垂直于它们的第三方向,因此我们可以计算“vecPerpLine”。休息使用点积和距离减法。这对我来说很有意义。有人可以评论我的想法吗?