查找点是否位于矩形内


82

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

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

上述方法需要旋转,因此需要浮点运算。还有其他有效的方法吗?


您可以快速检查一下该点是否落在旋转矩形的正交边界框中,如果不是,则快速失败。(是的,这只是答案的一半(嗯,由拐角点可以形成三个正交的盒子...而且太晚了(概念几何是最先使用的)))。
msw

但是我必须先旋转吗?
2010年

1
除非您告诉我们如何定义矩形,否则答案中没有太多实用价值。使用整数坐标时,用于选择形状的方法在选择算法时至关重要。
AnT 2010年

Answers:


80

矩形如何显示?三分?四分?点,边和角度?一分两分?还有吗 不知不觉中,任何回答您的问题的尝试都将仅具有学术价值。

在任何情况下,对于任何多边形(包括矩形)的测试是非常简单的:检查多边形的每个边缘,假设每个边缘被定向在反时针方向,以及测试是否点位于向左的边缘的(在左-手半平面)。如果所有边缘均通过测试,则该点在内部。如果至少有一个失败-该点在外面。

为了测试该点是否(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,则该点在线上。

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


这仅对数学家坐标集有效。在PC屏幕上和GPS方向上,轴的方向是不同的,因此不能确定一组正确的等式。或者您可以确定自己还没有。我的回答更好:-)。
Gangnus

AndreyT @Gangnus,精度高,您只需要检查凸形相对于P的所有点的方程符号是否相同,就可以不必担心坐标系或凸形的方向定义;))
杰森·罗杰斯

2
有几个扩展可以让您加快速度。1.由于矩形的两个相对边是平行的,因此它们的A,B系数可以相同。2.由于其他两个边垂直于它们,它们的系数A'B'可以由A'=B和给出B'=-A。3.A xp + B yp两条边都没有计算点,因此将它们合并为一个测试。然后,您在矩形中的测试变为(v_min < A xp + B yp < v_max) && ( w_min < B xp - A yp < w_max )
Michael Anderson

@MichaelAnderson又是什么v_min,等等?
匿名的

v_minA x + B y矩形内部所有值的最小值(在角处评估时的最小值)。v_max是相应的最大值。该w_?值是相同的,但对Bx - A y
迈克尔·安德森

41

假设矩形由三个点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),点(AB,AB)= 29,点(BC,BC)= 10。

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

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


1
0,2-10,2-10,10-2,10不是矩形。
埃里克·班维尔

2
请标出要点,并考虑验证您的第一条评论的准确性。
埃里克·班维尔

3
这是最好的答案!
塔里尔

1
我喜欢这个答案简洁明了,使操作次数或多或少与这里的其他好答案一样少,而且还具有非常直观和可视化的优点。
匿名

22

我从埃里克·班维尔的答案中借来的:

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.

这是一个Codepen,用于将输出绘制为可视测试:) http://codepen.io/mattburns/pen/jrrprN

在此处输入图片说明


嗨,@ matt burns,我使用了您的方法,并将其放在了我的测试项目中:jsfiddle.net/caymanbruce/06wjp2sk/6但我无法使其工作。不知道为什么它仍在不旋转的情况下测试原始矩形内的点。我mouseover在项目中使用了一个事件,因此,每当鼠标悬停在应该位于矩形内的点上时,它将在鼠标周围显示一个黑圈,而在矩形外则不显示任何内容。我需要帮助才能使其正常工作,但我很困惑。
newguy '16

mouseover应该是mousemove,只是拼写错误。
newguy '16


原则上,您的方法是正确的,但是您的示例中的矩形不是矩形。我在这里做了一个改进的版本,在这里我坚持原始的公式和命名方案,而输入实际上是一个真正的矩形。
JohannesB

15
# 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

读为:“如果将点连接到矩形的三个顶点,则这些线段和侧面之间的角度应为锐角”
P Shved 2010年

3
这样的方法的问题在于它们在理论上起作用,但是在实践中可能会遇到问题。OP没有说矩形的表示方式。这个答案假定它是由三个点来表示- abd。虽然从理论上讲,三点是表示任意矩形的有效方法,但实际上在一般情况下,不可能精确地进行整数坐标处理。通常情况下,将以平行四边形结束,该平行四边形非常接近矩形,但仍然不是矩形。
AnT

3
即该形状中的角度将不完全是90度。在这种情况下进行任何基于角度的测试时,必须非常小心。同样,这取决于OP如何为不精确表示的“矩形”定义“内部”。再次,关于如何表示矩形。
AnT 2010年

对您的两条评论+1。只有@avd可以告诉我们这是否足够好。
JonasElfström'5

非常适合我...经常使用三角函数和几何,不必提出公式来解决一个常见问题,这很好。谢谢。
2012年

12

我意识到这是一个旧线程,但是对于任何有兴趣从纯粹的数学角度来看这件事的人,数学堆栈交换中都有一个很棒的线程,这里:

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

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


您能总结到目前为止的讨论吗?否则,这可能应该是评论,而不是答案。有了更多代表,您就可以发表评论
内森·塔吉

7
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的Answer。我使用此代码检查像素的坐标(X,Y)是否位于形状内。


对您在此处的操作进行一些说明将非常有帮助。
布拉德(Brad)

只是想说一声谢谢。我将您为Unity Shader工作所需的工作转换为3分,而不是4分。效果很好!干杯。
达斯汀·詹森

它为我工作,这是我为Unity DOTS制作的C#实现:gist.github.com/rgoupil/04b59be8ddb56c992f25e1489c61b310
JamesDev

6

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

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


这是正确的,但是效率极低。
Gangnus

4

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

  • 让矩形由顶点A,B,C,D设置。点为P。坐标为矩形:x,y。
  • 让延长矩形的边。因此,我们有4条直线l AB,l BC,l CD,l DA,或者简称为l 1,l 2,l 3,l 4
  • 对每一个l i做一个方程。方程式如下:

    f i(P)= 0。

P是一点。对于属于l i的点,等式成立。

  • 我们需要方程式左侧的函数。它们是f 1,f 2,f 3,f 4
  • 请注意,对于来自l i一侧的每个点,函数f i大于0,对于来自另一侧f i的点小于0。
  • 因此,如果我们要检查P是否在矩形中,则只需要p在所有四行的正确边上即可。因此,我们必须检查四个功能的符号。
  • 但是,矩形所属的直线的哪一边是正确的呢?它是侧面,不位于直线上的矩形顶点位于此处。为了进行检查,我们可以选择两个不属于任何一个的顶点中的任何一个。
  • 因此,我们必须检查以下内容:

    f AB(P)f AB(C)> = 0

    f BC(P)f BC(D)> = 0

    f CD(P)f CD(A)> = 0

    f DA(P)f DA(B)> = 0

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

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

    f AB(P)≡(x A -x B)(y P -y B)-(y A -y B)(x P -x B

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

f AB(P)> = 0

f BC(P)> = 0

f CD(P)> = 0

f DA(P)> = 0

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

f AB(P)<= 0

f BC(P)<= 0

f CD(P)<= 0

f DA(P)<= 0

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


“ X位于顶部,Y位于右侧,我们必须将等式转向:”这取决于您如何确定“顺时针”。如果您认为坐标是数学坐标,则顺时针方向将自动变为逆时针方向,并且您仍然可以使用第一个完全相同的不等式。换句话说,如果您只是在所有方面都将坐标视为数学坐标,则可能不会出现混乱。反转或交换坐标不会对该谓词产生影响。
Palo

@Palo数学本身没有方向。是。但是该算法有几个要点,因此在任何点都具有可理解和合理的(在现实生活中)结果对于进行测试会更好。如果没有第二句的结尾,您将无法利用空间想象力检查是否正确解决了不等式。
Gangnus

0

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

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

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

通过将向量除以其大小来获得单位向量W',H'

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

除非我没记错,否则我认为我不是...(如果我错了,请纠正我),但是如果您的点在高度矢量上的投影大小小于高度矢量的大小(即矩形高度的一半)和点在宽度矢量上的投影大小是,那么您在矩形内部就有一个点。

如果您有通用坐标系,则可能必须使用矢量减法计算出高度/宽度/点矢量。矢量投影太神奇了!记住这一点。


0

继续亚光回答。我们需要使用 /math/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373 解决方案使其正常运行

下方无效
0 <=点(AB,AM)<=点(AB,AB)&& 0 <=点(BC,BM)<=点(BC,BC)

下面的作品
0 <=点(AB,AM)<=点(AB,AB)&& 0 <=点(AM,AC)<=点(AC,AC)

您可以通过以下方式将其粘贴在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});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.