【发布时间】:2010-11-16 02:21:31
【问题描述】:
判断一个点是否在平行四边形/菱形内最快的方法是什么?
【问题讨论】:
标签: actionscript-3 performance math mathematical-optimization
判断一个点是否在平行四边形/菱形内最快的方法是什么?
【问题讨论】:
标签: actionscript-3 performance math mathematical-optimization
再次您好,感谢您的所有回答。与此同时,我自己想出了一些我认为会相当快的方法:
假设我们有一个由 PQ 和 PR 跨越的平行四边形,其中 PQ 和 PR 是向量(P、Q 和 R 是角)。此外,我们还有要检查的点 A。
我们知道Vector PA可以拆分为平行于PQ和PR的两个向量:
PA=n*PQ+m*PR
现在我们知道 n 和 m 必须在区间 [0; 1],我们求解n和m:
n = -det(PA, PQ)/det(PQ, PR)
m = det(PA, PR)/det(PQ, PR)
其中 det(PA, PQ) 是向量 PA 和 PQ 的行列式:
det(PA, PQ) = PA.x*PQ.y-PQ.x*PA.y
如果点 A 在平行四边形内,则 0
var d:Number = det(PQ, PR);
if (0 <= -det(PA, PQ)/d <= 1 && 0 <= det(PA, PR)/d <= 1)
{
//inside
}
else
{
//outside
}
【讨论】:
PA 到PQ 的投影可能是负的,即使A 在平行四边形内。例如,如果PQ 和PR 是一个钝角,并且A 是一个非常靠近R 的内点,那么PA 到PQ 的投影是负的。
n 和 m 的公式不正确。据我所知,不依赖于PA.y。如果PR=(0,1)、PQ=(1,0) 和PA=(.5,1000),那么你的算法会说A 在里面。
det(PR,PQ)=+-Area(PR,PQ) 是由PR 和PQ 跨越的平行四边形的面积(如果PR 和PQ 处于“错误”顺序,则为负数)。所以你的公式说A 在平行四边形中,平行四边形的面积必须在PR 和PA 之间@ 和 PQ 必须介于 0 和 Area(PR,PQ) 之间。如果您绘制图片,这是非常直观的。不错的工作。 +1
想象一下从你的点向一个方向发出的光线。如果那条光线穿过你的形状线奇数次,它就在形状内部。如果它穿过偶数次,它就在形状之外。
因此,在您的程序中,您只需创建一条不可见的线并查看它交叉的频率。我想,Actionscript 可能有一个内置函数来执行此操作。
现在,如果您有大量的对象并且点只能在一个中,您可以使用Binary Space Partition 来存储对象的位置来加快速度。这样一来,您就不必将您的观点与每个对象进行比较,只需将其与附近的对象进行比较即可。
【讨论】:
查看我对this question 的回答,非常相似。在那里,如果平行四边形的一个角在(0,0) 处,我给出了我认为非常简单的测试,因为它使解释更容易查看,但修改它以使其正常工作并不难。
编辑:由于问题所有者熟悉向量,我基本上会用那种语言重写我的答案。假设平行四边形由向量PQ 和PR 组成,其中P、Q 和R 是角。符号* 将表示点积。选择一个点q 使得PQ 垂直于Pq(即Pq*PQ=0)和PR*Pq>0(例如,您可以通过将Q 围绕P 旋转90 度得到q) .还要选择一个点r,这样PR*Pr=0 和PQ*Pr>0。那么一个点A 在内部当且仅当(0 < Pr*PA < Pr*PQ) && (0 < Pq*PA < Pq*PR)。
【讨论】:
paper 描述了一种确定光线和四边形相交位置的方法。如果四边形是平行四边形,则可以进一步简化。
如果你有一个平行四边形,其相邻边由向量 AB 和 AC 描述。平行四边形平面上的任意一点都可以用下面的向量来描述
T(a, b) = A + a * AB + b * AC
任何射线都可以描述为原点O和方向D
R(t) = O + t * D
2的交集是当T(a, b) == R(t)
O + t * D = A + a * AB + b * AC
解决a 和b 的问题,并检查它们是否都在0 和1 之间。请参阅本文末尾的伪代码了解如何实现。
【讨论】:
直线的标准方程为 ax+bx+c=0 .. 但有趣的是,如果您采用该表达式 ax+bx+c,并在给定 a,b,c 的情况下计算点 x,y在特定的行中,您会发现表达式将平面分成两半,一半在表达式大于零的位置,另一半在小于零的位置。
现在,如果您取平行四边形的四个点,并计算构成平行四边形一侧的每条线的 a、b、c 系数,您可以计算所讨论的 x、y 的每个表达式并找到该点在每条线的哪一侧。平行四边形的 inside 将是特定边的逻辑与。
也就是说,一旦你为四行中的每一行设置了 a、b、c,你就可以进行测试 something like
if ( ((a1*x+b1*y+c1)>0) && ((a2*x+b2*y+c2)<0) &&
((a3*x+b3*y+c3)<0) && ((a4*x+b4*y+c4)>0) {
// it's in!
}
.. 唯一剩下的技巧是您必须确定每个符号测试的“极性”,即大于或小于零。执行此操作的简单方法是评估 0,0,并查看它是否在您想要的一侧,这相当于说“c”的符号告诉您测试的方式。
诚然,这是一种蛮力的方法,但它可以扩展到任何凸多边形......或者,删除一个点,它也适用于三角形。
【讨论】:
我对这个问题的第一个观察是矩形(与轴对齐)是一个简单的退化案例。如果该矩形的两个角是:(x1,y1) 和 (x2,y2),那么您只需测试,给定一个点 (x3,y3),min(x1,x2)
这也可能是一个有用的优化。如果我们找到平行四边形的轴对齐边界矩形,那么我们可以开始快速测试任何给定点。
如果我们的平行线具有非零斜率,那么我们计算边界线的轴交点以及在这些斜率处通过相关点的线的交点。如果我们的两个点的交点(由两个斜率定义)都位于边界线的交点之间,那么我们就在其中。如果其中任何一个不在这些范围内,那么我们不在。
我没有时间编写示例,但计算这些斜率和交叉点是第一年的代数。
[附录]
我现在看到第一篇文章(关于从要测试的点发出的光线并沿着任意斜率延伸)是对任何封闭平面多边形的这个问题的一般解决方案的参考......或者,事实上,对于任何闭合的平面曲线。它还可以扩展到封闭曲面的三个维度。
但是,有一个警告适用于平行四边形或菱形。在凹多边形(或其他一些曲线)的情况下,如果光线击中任何顶点(角),那么测试可能会返回偶数个“线”交叉点。换句话说,同时包含在多边形多个“边”中的“曲线”的任何部分都可能返回误报。
两种解决方案是:明确测试线段限制(角/顶点)处的交点,或将每个线段视为一端以 (point + epsilon) 为界(这样我们的计算将找不到任何点任何两侧的共同点)。
我找到一个边界矩形的想法仍然是一个有用的快速测试,并且通常扩展到所有多边形。我们找到 min() 和 max() x 来找到左右边界,而 min() 和 max() y 来找到上下边界。这也可以扩展到三个维度......一位朋友告诉我,标准图形库在大多数虚拟现实和 MMORPG 等中广泛使用它来进行碰撞检测。当在边界框中找到碰撞时,它们会进行更细粒度的计算在其中包含的多边形上。
【讨论】:
如果平行四边形是凸的(并且给定平行四边形的定义,它必须是 xD),那么任何测试它是否在其边界内的算法都应该这样做,您可以提高展开循环的效率,因为您知道只有 4 个顶点,例如。
这是一个简单的算法,它根据矢量乘积的右手定则测试点在所有线段的同一侧(您也可以通过用简单的符号测试替换除法以标准化矢量来优化它):
How to test if a point is inside of a convex polygon in 2D integer coordinates?
另一种选择,如果您要对同一个平行四边形进行大量比较,则将其归一化为正方形,获取进行转换的矩阵,每次得到要测试的点时,将其乘以矩阵然后检查转换后的点是否在归一化正方形内(这应该容易得多)。
【讨论】:
dist1 = cv2.pointPolygonTest(contours[0], (50, 70), True)
dist 返回以下三个之一:
【讨论】:
y 坐标是最简单的,所以从它开始。如果该点的 y 坐标在形状的顶部和底部之间,则继续到 x 坐标。在该点的y坐标处计算形状左右两侧的x坐标,并检查该点的x坐标是否在它们之间。
编辑:
给定左上角、右上角、右下角和左下角四个坐标:
if (y >= y1 && y <= y3) {
var k = (x4 - x1) / (y4 - y1);
var m = x1 - k * y1;
if (x >= k * y + m) {
k = (x3 - x2) / (y3 - y2);
m = x2 - k * y2;
if (x <= k * y + m) {
// inside
}
}
}
【讨论】: