我想确定一个点是否在一个矩形内,这个矩形可以以任意方式定向,不一定是轴向对齐的。
我能想到的一种方法是将矩形和点坐标旋转,使矩形轴向对齐,然后通过测试点的坐标是否在矩形内来确定。
以上方法需要旋转和浮点运算。有没有其他有效的方法来做到这一点?
我想确定一个点是否在一个矩形内,这个矩形可以以任意方式定向,不一定是轴向对齐的。
我能想到的一种方法是将矩形和点坐标旋转,使矩形轴向对齐,然后通过测试点的坐标是否在矩形内来确定。
以上方法需要旋转和浮点运算。有没有其他有效的方法来做到这一点?
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
的值具有相同的大小但对于相对的(即平行的)边缘具有不同的符号,可以利用这一点来简化测试。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 Andersonv_min
是什么,等等? - pretzlstylev_min
是矩形内所有值 A x + B y
的最小值(在角落处计算时为最小值)。v_max
是相应的最大值。w_?
值也是一样的,但是针对的是 Bx - A y
。 - Michael AndersonA x + B y
和 B x - A y
的值应该是相同的吗? - pretzlstyle假设矩形由三个点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 <= 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
mouseover
事件,因此每当鼠标悬停在应该在矩形内的点上时,它将在鼠标周围显示一个黑色圆点,并且在矩形外部它不应该显示任何东西。我需要帮助让它工作,但我很困惑。 - newguymouseover
应该是 mousemove
,只是打错了。 - newguy# 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
a
,b
和d
表示。虽然在理论上三个点是表示任意矩形的一种有效方式,但在实践中,通常情况下无法用整数坐标精确地表示一个矩形。一般情况下,最终会得到一个平行四边形,该平行四边形非常接近于矩形,但仍不是一个矩形。 - AnT stands with Russia我知道这是一个旧的帖子,但对于任何对纯数学角度感兴趣的人来说,这里有一个在数学堆栈交换上的优秀帖子:
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
之间。这相当于沿着矩形的长度和宽度取向量p - p1
的投影,以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"
就是这样。它也适用于平行四边形。
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的答案。我使用这段代码来检查像素的坐标(X,Y)是否在形状内。
为每个li列出一个方程:
fi(P)=0.
P是一个点。对于属于li的点,该方程成立。
因此,我们需要检查以下内容:
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) <= 0
fBC(P) <= 0
fCD(P) <= 0
fDA(P) <= 0
如果您不确定轴的方向,请小心使用此简化检查 - 使用已知位置的一个点检查您选择的正确不等式。
在继续回答 Matt 的问题之前,我们需要使用以下方案https://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373 以使其正常工作。
以下代码不起作用:
0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)
以下代码可行:
0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(AM,AC) <= dot(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});