我有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得到了“奖品”:)
答案 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三联的边界框内的少数几个。然后像你提到的那样做一个更详细的测试。