【问题标题】:Finding whether a point lies inside a rectangle or not判断一个点是否在矩形内
【发布时间】:2010-05-02 07:08:15
【问题描述】:

我想知道一个点是否在一个矩形内。矩形可以以任何方式定向,并且不需要轴对齐。

我能想到的一种方法是旋转矩形和点坐标以使矩形轴对齐,然后简单地测试点的坐标是否在矩形的坐标内。

上述方法需要旋转,因此需要浮点运算。有没有其他有效的方法来做到这一点?

【问题讨论】:

  • 您可以快速检查该点是否落在旋转矩形的正交边界框中,如果没有则快速失败。 (是的,这只是答案的一半(嗯,角点可以形成三个正交框......而且已经很晚了(概念几何是第一个出现的))。
  • 但我必须先旋转对吗?
  • 在你告诉我们你的矩形是如何定义的之前,答案不会有太大的实用价值。当您使用整数坐标时,用于表示形状的方法在选择算法时至关重要。
  • 也许这个简单的答案是可以的programming-idioms.org/idiom/178/… ?

标签: algorithm geometry


【解决方案1】:

矩形是如何表示的?三分?四分?点、边、角?两点加边?还有什么?在不知道这一点的情况下,任何试图回答您的问题的尝试都只会具有纯粹的学术价值。

在任何情况下,对于任何多边形(包括矩形),测试都很简单:检查多边形的每条边,假设每条边都是逆时针方向,并测试该点是否位于在边缘的左侧(在左侧半平面中)。如果所有边都通过测试 - 点在里面。如果至少有一个失败 - 点在外面。

为了测试点(xp, yp)是否在边(x1, y1) - (x2, y2)的左侧,你只需要计算一下

D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1)

如果D > 0,则该点位于左侧。如果D < 0,则该点位于右侧。如果D = 0,点就在线上。


此答案的先前版本描述了看似不同版本的左侧测试(见下文)。但是很容易证明它计算的值是一样的。

... 为了测试点(xp, yp) 是否位于边(x1, y1) - (x2, y2) 的左侧,您需要为包含边的直线建立直线方程。等式如下

A * x + B * y + C = 0

在哪里

A = -(y2 - y1)
B = x2 - x1
C = -(A * x1 + B * y1)

现在你需要做的就是计算

D = A * xp + B * yp + C

如果D > 0,点在左侧。如果D < 0,则该点位于右侧。如果D = 0,则该点在线上。

但是,此测试同样适用于任何凸多边形,这意味着它对于矩形来说可能过于通用。矩形可能允许进行更简单的测试...例如,在矩形(或任何其他平行四边形)中,AB 的值具有相同的大小,但相对(即平行)边缘的符号不同,这可以被用来简化测试。

【讨论】:

  • 这仅适用于数学家坐标集。在 PC 屏幕和 GPS 中,轴的方向是不同的,你不能确定你有正确的不等式集。或者你可以确定你没有。我的答案更好:-)。
  • AndreyT @Gangnus,快速精度,你只需要检查方程的符号对于与P相关的凸形状的所有点是否相同,这样你就不用担心坐标了系统或定义凸形的方向;))
  • 有几个扩展可以让你加快速度。 1. 由于矩形的相对两侧平行,所以它们的 A、B 系数可以相同。 2. 由于另外两条边与它们垂直,它们的系数A'B' 可以由A'=BB'=-A 给出。 3. 计算两个边缘的A xp + B yp 没有意义,因此将它们组合成一个测试。那么你在一个矩形中的测试就变成了(v_min < A xp + B yp < v_max) && ( w_min < B xp - A yp < w_max )
  • @MichaelAnderson v_min 等是什么?
  • v_min 是矩形内部所有值的A x + B y 的最小值(这是在角处评估时的最小值)。 v_max 是对应的最大值。 w_? 的值是相同的,但对于 Bx - A y
【解决方案2】:

假设矩形由三个点A,B,C表示,AB和BC垂直,你只需要检查查询点M在AB和BC上的投影:

0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)

AB 是向量 AB,坐标为 (Bx-Ax,By-Ay),dot(U,V) 是向量 U 和 V 的点积:Ux*Vx+Uy*Vy

更新。让我们举个例子来说明这一点:A(5,0) B(0,2) C(1,5) 和 D(6,3)。 从点坐标得到AB=(-5,2), BC=(1,3), dot(AB,AB)=29, dot(BC,BC)=10。

对于查询点 M(4,2),我们有 AM=(-1,2), BM=(4,0), dot(AB,AM)=9, dot(BC,BM)=4。 M 在矩形内。

对于查询点 P(6,1),我们有 AP=(1,1), BP=(6,-1), dot(AB,AP)=-3, dot(BC,BP)=3 . P不在矩形内,因为它在AB边上的投影不在线段AB内。

【讨论】:

  • 0,2 - 10,2 - 10,10 - 2,10 不是矩形。
  • 请绘制要点,并考虑验证您的第一条评论的准确性。
  • 这是最好的答案!
  • 我喜欢这个答案简洁明了,使操作数量或多或少与这里其他好的答案一样少,但也具有非常直观和可视化的优点。
【解决方案3】:

我借鉴了 Eric Bainville 的回答:

0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)

在 javascript 中如下所示:

function pointInRectangle(m, r) {
    var AB = vector(r.A, r.B);
    var AM = vector(r.A, m);
    var BC = vector(r.B, r.C);
    var BM = vector(r.B, m);
    var dotABAM = dot(AB, AM);
    var dotABAB = dot(AB, AB);
    var dotBCBM = dot(BC, BM);
    var dotBCBC = dot(BC, BC);
    return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}

function vector(p1, p2) {
    return {
            x: (p2.x - p1.x),
            y: (p2.y - p1.y)
    };
}

function dot(u, v) {
    return u.x * v.x + u.y * v.y; 
}

例如:

var r = {
    A: {x: 50, y: 0},
    B: {x: 0, y: 20},
    C: {x: 10, y: 50},
    D: {x: 60, y: 30}
};

var m = {x: 40, y: 20};

然后:

pointInRectangle(m, r); // returns true.

这是一个用于将输出绘制为视觉测试的代码笔 :) http://codepen.io/mattburns/pen/jrrprN

【讨论】:

  • 嗨@matt Burns 我使用了你的方法并将其放入我的测试项目中:jsfiddle.net/caymanbruce/06wjp2sk/6 但我无法让它工作。不知道为什么它仍在测试原始矩形内的点而没有旋转。我在我的项目中使用了mouseover 事件,所以每当鼠标移到应该在矩形内的点上时,它会在鼠标周围显示一个黑色圆点,而在矩形外它应该什么也不显示。我需要帮助才能让它工作,但我很困惑。
  • mouseover 应该是 mousemove,只是错字。
  • 原则上你的方法是正确的,但你的矩形不是你的例子中的矩形。我在here 上做了你的改进版本,我坚持原来的公式和命名方案,输入实际上是一个真正的矩形。
【解决方案4】:
# Pseudo code
# Corners in ax,ay,bx,by,dx,dy
# Point in x, y

bax = bx - ax
bay = by - ay
dax = dx - ax
day = dy - ay

if ((x - ax) * bax + (y - ay) * bay < 0.0) return false
if ((x - bx) * bax + (y - by) * bay > 0.0) return false
if ((x - ax) * dax + (y - ay) * day < 0.0) return false
if ((x - dx) * dax + (y - dy) * day > 0.0) return false

return true

【讨论】:

  • 读作:“如果我们将点连接到矩形的三个顶点,那么这些线段和边之间的角度应该是锐角”
  • 这种方法的问题在于它们在理论上可行,但在实践中可能会遇到问题。 OP没有说明矩形是如何表示的。此答案假定它由三个点表示 - abd。虽然理论上三点是表示任意矩形的有效方式,但在实践中,一般情况下不可能精确地在整数坐标中进行。在一般情况下,最终会得到一个平行四边形,它非常接近矩形,但仍然不是矩形。
  • 即该形状的角度不会正好是 90 度。在这种情况下进行任何基于角度的测试时,必须非常小心。同样,这取决于 OP 如何为不精确表示的“矩形”定义“内部”。还有,关于矩形是如何表示的。
  • +1 给你的两个 cmets。只有@avd 可以告诉我们这是否足够好。
  • 非常适合我……经常使用三角学和几何学,不用想出一个公式来解决一个常见问题真是太好了。谢谢。
【解决方案5】:

我知道这是一个旧线程,但对于任何有兴趣从纯数学角度看待这个问题的人,这里有一个关于数学堆栈交换的优秀线程:

https://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle

编辑:受此线程的启发,我整理了一个简单的矢量方法,用于快速确定您的观点所在。

假设你有一个矩形,点在 p1 = (x1, y1), p2 = (x2, y2), p3 = (x3, y3) 和 p4 = (x4, y4),顺时针方向。如果点 p = (x, y) 位于矩形内,则点积 (p - p1).(p2 - p1) 将位于 0 和 |p2 - p1|^2 和 (p - p1) 之间。 (p4 - p1) 将介于 0 和 |p4 - p1|^2 之间。这相当于以p1为原点,沿矩形的长宽方向对向量p-p1进行投影。

如果我显示等效代码,这可能更有意义:

p21 = (x2 - x1, y2 - y1)
p41 = (x4 - x1, y4 - y1)

p21magnitude_squared = p21[0]^2 + p21[1]^2
p41magnitude_squared = p41[0]^2 + p41[1]^2

for x, y in list_of_points_to_test:

    p = (x - x1, y - y1)

    if 0 <= p[0] * p21[0] + p[1] * p21[1] <= p21magnitude_squared:
        if 0 <= p[0] * p41[0] + p[1] * p41[1]) <= p41magnitude_squared:
            return "Inside"
        else:
            return "Outside"
    else:
        return "Outside"

就是这样。它也适用于平行四边形。

【讨论】:

【解决方案6】:
bool pointInRectangle(Point A, Point B, Point C, Point D, Point m ) {
    Point AB = vect2d(A, B);  float C1 = -1 * (AB.y*A.x + AB.x*A.y); float  D1 = (AB.y*m.x + AB.x*m.y) + C1;
    Point AD = vect2d(A, D);  float C2 = -1 * (AD.y*A.x + AD.x*A.y); float D2 = (AD.y*m.x + AD.x*m.y) + C2;
    Point BC = vect2d(B, C);  float C3 = -1 * (BC.y*B.x + BC.x*B.y); float D3 = (BC.y*m.x + BC.x*m.y) + C3;
    Point CD = vect2d(C, D);  float C4 = -1 * (CD.y*C.x + CD.x*C.y); float D4 = (CD.y*m.x + CD.x*m.y) + C4;
    return     0 >= D1 && 0 >= D4 && 0 <= D2 && 0 >= D3;}





Point vect2d(Point p1, Point p2) {
    Point temp;
    temp.x = (p2.x - p1.x);
    temp.y = -1 * (p2.y - p1.y);
    return temp;}

我刚刚使用 c++ 实现了 AnT's Answer。我使用此代码检查像素的坐标(X,Y)是否位于形状内。

【讨论】:

  • 对您在这里所做的事情进行一些解释会非常有帮助。
  • 只是想说声谢谢。我将你必须为 Unity Shader 工作的内容转换为 3 点而不是 4 点。效果很好!干杯。
  • 它对我有用,这是我为 Unity DOTS 制作的 C# 实现:gist.github.com/rgoupil/04b59be8ddb56c992f25e1489c61b310
【解决方案7】:

如果您无法解决矩形的问题,请尝试将问题分解为更简单的问题。 将矩形分成 2 个三角形,检查点是否在其中任何一个内部,就像他们在 here 中解释的那样@

基本上,您从一个点开始循环遍历每两对线的边缘。然后使用叉积检查点是否在使用叉积的两条线之间。如果对所有 3 个点都进行了验证,则该点在三角形内。这种方法的好处是它不会产生任何检查角度时会发生的浮点错误。

【讨论】:

  • 这是正确的,但是非常无效的算法。
【解决方案8】:

如果一个点在一个矩形内。在飞机上。对于数学家或大地测量学 (GPS) 坐标

  • 让矩形由顶点 A、B、C、D 设置。点为 P。坐标为矩形:x、y。
  • 让我们延长矩形的边。所以我们有 4 条直线 lAB、lBC、lCD、lDA,或者,简而言之, l1, l2, l3, l4.
  • 为每个 li 做一个方程。方程排序:

    fi(P)=0。

P 是一个点。对于属于 li 的点,等式成立。

  • 我们需要方程左边的函数。它们是 f1、f2、f3、f4
  • 注意,对于来自 li 一侧的每个点,函数 fi 大于 0,对于来自另一侧的点 fi 小于 0。
  • 因此,如果我们检查 P 是否在矩形中,我们只需要 p 位于所有四行的正确边上。因此,我们必须检查四个函数的符号。
  • 但是直线的哪一侧是正确的,矩形属于哪一侧?它是边,矩形的顶点不属于该线。为了检查,我们可以选择两个不属于的顶点中的任何一个。
  • 所以,我们必须检查一下:

    fAB(P) fAB(C) >= 0

    fBC(P) fBC(D) >= 0

    fCD(P) fCD(A) >= 0

    fDA(P) fDA(B) >= 0

不等式并不严格,因为如果一个点在边界上,它也属于矩形。如果您不需要边界上的点,则可以将不等式更改为严格的不等式。但是当你从事浮点运算时,选择是无关紧要的。

  • 对于矩形中的一个点,所有四个不等式都是正确的。请注意,它也适用于每个凸多边形,只是线/方程的数量会有所不同。
  • 剩下的唯一事情就是得到一条穿过两点的线的方程。这是一个众所周知的线性方程。让我们把它写成线AB和点P:

    fAB(P)   ≡ (xA-xB) (yP-yB) - (yA-yB) (xP-xB)

检查可以简化 - 让我们沿着矩形顺时针 - A、B、C、D、A。然后所有正确的边都将位于线条的右侧。因此,我们不需要与另一个顶点所在的边进行比较。我们需要检查一组更短的不等式:

fAB(P) >= 0

fBC(P) >= 0

fCD(P) >= 0

fDA(P) >= 0

但这对于正常的数学家(来自学校数学)坐标集是正确的,其中 X 位于右侧,Y 位于顶部。对于 GPS 中使用的 大地测量 坐标,其中 X 位于顶部,Y 位于右侧,我们必须转换不等式:

fAB(P)

fBC(P)

fCD(P)

fDA(P)

如果你不确定轴的方向,小心这个简化的检查 - 检查一个已知位置的点,如果你选择了正确的不等式。

【讨论】:

  • "其中X在顶部,Y在右侧,我们必须转动不等式:"这取决于你如何确定“顺时针”。如果您认为坐标是数学坐标,顺时针将自动变为逆时针,您仍然可以使用第一组相同的不等式。换句话说,如果您只考虑坐标在所有方面都是数学坐标,那么您可能会很好地摆脱这种混乱。反转或交换坐标对这个谓词没有影响。
  • @Palo 数学本身没有方向。是的。但是该算法有几个要点,如果能够在任何一点上获得可理解和明智的(在现实生活中)结果,以便能够进行测试,那就更好了。如果没有到第二句话的结尾,您几乎无法使用您的空间想象力检查您是否正确解决了不等式。
【解决方案9】:

我想到的最简单的方法是将点投影到矩形的轴上。让我解释一下:

如果你能得到从矩形中心到顶部或底部边缘和左侧或右侧边缘的向量。而且您还有一个从矩形中心到您的点的向量,您可以将该点投影到您的宽度和高度向量上。

P = 点向量,H = 高度向量,W = 宽度向量

通过向量除以它们的大小得到单位向量 W', H'

proj_P,H = P - (P.H')H' proj_P,W = P - (P.W')W'

除非我弄错了,我不认为我是......(如果我错了,请纠正我)但是如果你的点在高度向量上的投影幅度小于高度向量的幅度(这是矩形高度的一半)并且您的点在宽度向量上的投影幅度是,那么您在矩形内有一个点。

如果您有一个通用坐标系,您可能需要使用向量减法计算出高度/宽度/点向量。矢量投影是惊人的!记住这一点。

【讨论】:

    【解决方案10】:

    继续马特回答。我们需要使用 https://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373 使其工作的解决方案

    以下不起作用
    0

    以下作品
    0

    您通过在 javascript 控制台中粘贴以下内容进行检查 //相同的Javascript解决方案

                var screenWidth = 320;
                var screenHeight = 568;
                var appHeaderWidth = 320;
                var AppHeaderHeight = 65;
                var regionWidth = 200;
                var regionHeight = 200;
    
                this.topLeftBoundary = {
                    A: {x: 0, y: AppHeaderHeight},
                    B: {x: regionWidth, y: AppHeaderHeight},
                    C: {x: 0, y: regionHeight + AppHeaderHeight},
                    D: {x: regionWidth, y: regionHeight + AppHeaderHeight}
                }
    
                this.topRightBoundary = {
                    A: {x: screenWidth, y: AppHeaderHeight},
                    B: {x: screenWidth - regionWidth, y: AppHeaderHeight},
                    C: {x: screenWidth, y: regionHeight + AppHeaderHeight},
                    D: {x: screenWidth - regionWidth, y: regionHeight + AppHeaderHeight}
                }
    
                this.bottomRightBoundary = {
                    A: {x: screenWidth, y: screenHeight},
                    B: {x: screenWidth - regionWidth, y: screenHeight},
                    C: {x: screenWidth, y: screenHeight - regionHeight},
                    D: {x: screenWidth - regionWidth, y: screenHeight - regionHeight}
                }
    
                this.bottomLeftBoundary = {
                    A: {x: 0, y: screenHeight},
                    B: {x: regionWidth, y: screenHeight},
                    C: {x: 0, y: screenHeight - regionHeight},
                    D: {x: regionWidth, y: screenHeight - regionHeight}
                }
                console.log(this.topLeftBoundary);
                console.log(this.topRightBoundary);
                console.log(this.bottomRightBoundary);
                console.log(this.bottomLeftBoundary);
    
                checkIfTapFallsInBoundary = function (region, point) {
                    console.log("region " + JSON.stringify(region));
                    console.log("point" + JSON.stringify(point));
    
                    var r = region;
                    var m = point;
    
                    function vector(p1, p2) {
                        return {
                            x: (p2.x - p1.x),
                            y: (p2.y - p1.y)
                        };
                    }
    
                    function dot(u, v) {
                        console.log("DOT " + (u.x * v.x + u.y * v.y));
                        return u.x * v.x + u.y * v.y;
                    }
    
                    function pointInRectangle(m, r) {
                        var AB = vector(r.A, r.B);
                        var AM = vector(r.A, m);
                        var AC = vector(r.A, r.C);
                        var BC = vector(r.B, r.C);
                        var BM = vector(r.B, m);
    
                        console.log("AB " + JSON.stringify(AB));
                        console.log("AM " + JSON.stringify(AM));
                        console.log("AM " + JSON.stringify(AC));
                        console.log("BC " + JSON.stringify(BC));
                        console.log("BM " + JSON.stringify(BM));
    
                        var dotABAM = dot(AB, AM);
                        var dotABAB = dot(AB, AB);
                        var dotBCBM = dot(BC, BM);
                        var dotBCBC = dot(BC, BC);
                        var dotAMAC = dot(AM, AC);
                        var dotACAC = dot(AC, AC);
    
                        console.log("ABAM " + JSON.stringify(dotABAM));
                        console.log("ABAB " + JSON.stringify(dotABAB));
                        console.log("BCBM " + JSON.stringify(dotBCBM));
                        console.log("BCBC " + JSON.stringify(dotBCBC));
                        console.log("AMAC " + JSON.stringify(dotAMAC));
                        console.log("ACAC" + JSON.stringify(dotACAC));
    
                        var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotBCBM && dotBCBM <= dotBCBC));
                        console.log(" first check" + check);
                        var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotAMAC && dotAMAC <= dotACAC));
                        console.log("second check" + check);
                        return check;
                    }
    
                    return pointInRectangle(m, r);
                }
    
            //var point = {x: 136, y: 342};
    
                checkIfTapFallsInBoundary(topLeftBoundary, {x: 136, y: 342});
                checkIfTapFallsInBoundary(topRightBoundary, {x: 136, y: 274});
                checkIfTapFallsInBoundary(bottomRightBoundary, {x: 141, y: 475});
                checkIfTapFallsInBoundary(bottomRightBoundary, {x: 131, y: 272});
                checkIfTapFallsInBoundary(bottomLeftBoundary, {x: 131, y: 272});
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 2022-10-22
      • 1970-01-01
      • 2023-03-31
      • 2019-01-25
      相关资源
      最近更新 更多