快速几何接近谓词

时间:2008-10-31 21:12:16

标签: algorithm language-agnostic optimization geometry performance

我有3个点(A,B和X)和距离(d)。我需要创建一个函数来测试点X是否比距离d更接近线段AB上的任何点。

问题首先是,我的解决方案是否正确,然后提出更好(更快)的解决方案。

我的第一次传递如下

AX = X-A
BX = X-B
AB = A-B

    // closer than d to A (done squared to avoid needing to compute the sqrt in mag)
If d^2 > AX.mag^2  return true

    // closer than d to B
If d^2 > BX.mag^2 return true

    // "beyond"  B
If (dot(BX,AB) < 0) return false

    // "beyond"  A
If (dot(AX,AB) > 0) return false

    // find component of BX perpendicular to AB
Return (BX.mag)^2 - (dot(AB,BX)/AB.mag)^2 < d^2

这段代码最终会被运行一大堆P和一大堆A / B / d三元组,目的是找到所有通过至少一个A / B / d的P,所以我怀疑那里是一种基于此降低总体成本的方法,但我还没有考虑过。

(顺便说一句:我知道一些重新排序,一些临时值和一些代数身份可能会降低上述成本。为了清楚起见,我只是省略了它们。)


编辑:这是一个2D问题(但推广到3D的解决方案很酷

编辑:经过进一步思考,我预计命中率约为50%。 X点可以在嵌套层次结构中形成,因此我希望能够将大型子树修剪为全通和全失败。限制三胞胎的A / B / D将更具诀窍。

编辑:d与AB的数量级相同。


编辑:Artelius发布了一个很好的解决方案。在我完全理解它之前,我不确定我是否正确理解了他所得到的东西。无论如何,结果出现了另一个想法:

  • 第一个Artelius'位,预先确定一个矩阵,它将AB放置在原点的中心并与X轴对齐。 (2加,4 muls和2加)
  • 将它全部折叠到第一象限(2 abs)
  • 在X&amp; Y中缩放以使区域的中心部分成单位正方形(2 mul)
  • 测试点是否在那个方格(2测试)是如此退出
  • 测试端盖(返回未缩放的值
    • 在x中翻译以将结尾放在原点(1添加)
    • square并添加(2 mul,1 add)
    • 与d ^ 2(1 cmp)比较

我很确定这会击败我的解决方案。

(如果没有什么比这更好的了,Artelius得到了“奖品”:)

5 个答案:

答案 0 :(得分:2)

嗯......什么是命中率?点“X”多长时间符合接近要求?

我认为您现有的算法很好,任何其他优化都将来自调整到真实数据。例如,如果“X”点在99%的时间内都符合接近度测试,那么我认为您的优化策略应该与仅在1%的时间内通过测试的情况大不相同。


顺便提一下,当你到达运行此算法的点数为数千点时,你应该将所有点组织成一个K维树(或KDTree)。它使“最近邻居”的计算更加简单。

你的问题比基本的最近邻居更复杂(因为你正在检查接近线段但不仅仅是接近点)但我仍然认为KDTree会是方便。

答案 1 :(得分:2)

如果我读得正确,那么这几乎与3D光线追踪中使用的经典光线/球体相交测试相同。

在这种情况下,您在半径为d的位置X处有一个球体,并且您正在尝试找出AB线是否与球体相交。与光线追踪的一个区别在于,在这种情况下,你有一条特定的线AB,而在光线追踪中,光线通常被推广为origin + distance * direction,而你并不关心无限远的距离它是AB+行。

来自我自己的光线跟踪器的伪代码(基于“光线跟踪简介”(编辑格拉纳)中给出的算法):

Vector v = X - A
Vector d = normalise(B - A)  // unit direction vector of AB
double b = dot(v, B - A)
double discrim = b^2 - dot(v, v) + d^2
if (discrim < 0)
     return false            // definitely no intersection

如果你已经做到这一点,那么有一些机会满足你的条件。你只需要确定交叉点是否在AB线上:

discrim = sqrt(discrim)
double t2 = b + discrim
if (t2 <= 0)
    return false             // intersection is before A

double t1 = b  - discrim

result = (t1 < length(AB) || (t2 < length(AB))

答案 2 :(得分:2)

如果您的(A,B,d)集合固定,您可以计算每个矩阵对转换坐标系统,使AB线成为X轴,AB的中点为起源。

认为这是构建矩阵的一种简单方法:

trans = - ((A + B) / 2)        // translate midpoint of AB to origin

rot.col1 = AB / AB.mag         // unit vector in AB direction

                        0 -1    
rot.col2 = rot.col1 * (      ) // unit vector perp to AB
                        1  0 

rot = rot.inverse()            // but it needs to be done in reverse

然后你只需拿走每个X并做rot * (X + trans)

有问题的区域现在在x和y轴上实际上是对称的,所以你可以得到x坐标和y坐标的绝对值。

然后你只需要检查:

y < d && x < AB.mag/2            //"along" the line segment
|| (x - AB.mag/2)^2 + y^2 < d^2  // the "end cap".

你可以做另一招;矩阵可以缩小AB.mag/2因子(记住矩阵只计算一次(A,B),这意味着找到它们比实际检查本身更慢)。这意味着您的支票变为:

y < 2*d/AB.mag && x < 1
|| (x - 1)^2 + y^2 < (2*d/AB.mag)^2

用常数1替换了AB.mag / 2的两个实例,可能触摸速度更快。当然,您也可以预先计算2*d/AB.mag(2*d/AB.mag)^2

这是否会比其他方法更快地取决于您提供的输入。但是如果AB的长度与d相比很长,我认为它会比你发布的方法快得多。

答案 3 :(得分:1)

  

这段代码最终会被运行一大堆P和一大堆A / B / d三元组,目的是找到所有通过至少一个A / B / d的P,所以我怀疑那里是一种基于此降低总体成本的方法,但我还没有考虑过。

在d~AB的情况下,对于给定的点X,您可以首先测试X是否属于半径d和中心Ai或Bi的许多球体之一。看图片:

     ......        .....
  ...........   ...........
 ...........................
.......A-------------B.......
 ...........................
  ...........   ...........
     .....         .....

前两个测试

If d^2 > AX.mag^2 return true
If d^2 > BX.mag^2 return true

是最快的,如果d~AB,它们也是成功概率最高的那些(考虑到测试成功)。给定X,您可以先进行所有“球体测试”,然后进行所有最终测试:

Return (BX.mag)^2 - (dot(AB,BX)/AB.mag)^2 

答案 4 :(得分:1)

如果A / B / d的集合数量很大,并且您肯定是2D,请考虑使用R-trees(或它们的八边形等价物),其中R树中的每个条目都是最小边界盒子的A / B / d三联。这样就可以省去测试很多A / B / d三重奏和将CPU功率仅集中在每个点X在A / B / d三联的边界框内的少数几个。然后像你提到的那样做一个更详细的测试。