非常快速的3D距离检查?

时间:2010-09-12 02:29:48

标签: c++ algorithm

有没有办法在结果粗糙的地方进行快速而肮脏的3D距离检查,但速度非常快?我需要做深度排序。我像这样使用STL sort

bool sortfunc(CBox* a, CBox* b)
{
    return a->Get3dDistance(Player.center,a->center) <
      b->Get3dDistance(Player.center,b->center);
}

float CBox::Get3dDistance( Vec3 c1, Vec3 c2 )
{
    //(Dx*Dx+Dy*Dy+Dz*Dz)^.5 
    float dx = c2.x - c1.x;
    float dy = c2.y - c1.y;
    float dz = c2.z - c1.z;

return sqrt((float)(dx * dx + dy * dy + dz * dz));
}

是否有可能在没有平方根或可能没有乘法的情况下进行此操作?

13 个答案:

答案 0 :(得分:64)

您可以省略平方根,因为对于所有正(或实际,非负)数字xy,如果sqrt(x) < sqrt(y)x < y。由于你是实数的平方和,每个实数的平方都是非负的,任何正数之和都是正数,平方根条件都是正确的。

但是,如果不更改算法,则无法消除乘法。这是一个反例:如果x是(3,1,1)而y是(4,0,0),|x| < |y|是因为sqrt(1*1+1*1+3*3) < sqrt(4*4+0*0+0*0)1*1+1*1+3*3 < 4*4+0*0+0*0 ,但是1+1+3 > 4+0+0

由于现代CPU可以比从内存中实际加载操作数更快地计算点积,因此无论如何都不可能通过消除乘法获得任何收益(我认为最新的CPU有一个特殊的指令可以计算一个每3个循环点产品!)。

如果不首先进行一些分析,我不会考虑更改算法。您选择的算法在很大程度上取决于数据集的大小(它是否适合缓存?),运行它的频率,以及您对结果的处理方式(碰撞检测?邻近?遮挡?)。

答案 1 :(得分:26)

我通常首先按Manhattan distance

过滤
float CBox::Within3DManhattanDistance( Vec3 c1, Vec3 c2, float distance )
{
    float dx = abs(c2.x - c1.x);
    float dy = abs(c2.y - c1.y);
    float dz = abs(c2.z - c1.z);

    if (dx > distance) return 0; // too far in x direction
    if (dy > distance) return 0; // too far in y direction
    if (dz > distance) return 0; // too far in z direction

    return 1; // we're within the cube
}

实际上,如果您对环境有更多了解,可以进一步优化。例如,在像飞行模拟器或第一人称射击游戏那样存在地面的环境中,水平轴比垂直轴大得多。在这样的环境中,如果两个物体相距很远,则它们很可能被x轴和y轴而不是z轴分开(在第一人称射击者中,大多数物体共享相同的z轴)。因此,如果您首先比较x和y,您可以从函数中提前返回并避免进行额外的计算:

float CBox::Within3DManhattanDistance( Vec3 c1, Vec3 c2, float distance )
{
    float dx = abs(c2.x - c1.x);
    if (dx > distance) return 0; // too far in x direction

    float dy = abs(c2.y - c1.y);
    if (dy > distance) return 0; // too far in y direction

    // since x and y distance are likely to be larger than
    // z distance most of the time we don't need to execute
    // the code below:

    float dz = abs(c2.z - c1.z);
    if (dz > distance) return 0; // too far in z direction

    return 1; // we're within the cube
}

抱歉,我没有意识到该功能用于排序。您仍然可以使用Manhattan distance进行非常粗略的第一次排序:

float CBox::ManhattanDistance( Vec3 c1, Vec3 c2 )
{
    float dx = abs(c2.x - c1.x);
    float dy = abs(c2.y - c1.y);
    float dz = abs(c2.z - c1.z);

    return dx+dy+dz;
}

在粗略的第一次排序之后,你可以获得最顶级的结果,比如前十名最接近的玩家,并使用适当的距离计算重新排序。

答案 2 :(得分:16)

这是一个可以帮助你摆脱sqrt和multiply的等式:

max(|dx|, |dy|, |dz|) <= distance(dx,dy,dz) <= |dx| + |dy| + |dz|

这可以获得距离的范围估计值,将其降低到3倍(上限和下限最多相差3倍)。然后,您可以对较低的数字进行排序。然后,您需要处理数组,直到到达比第一个遮蔽对象远3x的对象。然后,您可以保证找不到数组中较晚的任何对象。

顺便说一下,这里排序太过分了。一种更有效的方法是制作一系列具有不同距离估计值的桶,比如[1-3],[3-9],[9-27],......然后将每个元素放入桶中。从最小到最大处理铲斗,直到到达模糊物体。处理1个额外的桶只是为了确定。

顺便说一下,浮点数乘法现在相当快。通过将其转换为绝对值,我不确定你获得了多少收益。

答案 3 :(得分:9)

我很失望,那些伟大的数学技巧似乎正在迷失。以下是您要求的答案。来源是Paul Hsieh的优秀网站:http://www.azillionmonkeys.com/qed/sqroot.html。请注意,你不关心距离;你可以用距离的平方来做得很好,这会更快。


在2D中,我们可以得到距离度量的粗略近似值,其中没有平方根,其公式为:

  

distanceapprox(x,y)= (1 + 1/(4-2*√2))/2 * min((1 / √2)*(|x|+|y|), max (|x|, |y|)) http://i52.tinypic.com/280tbnc.gif

这将偏离真正的答案最多约8%。 3维的类似推导导致:

  

distanceapprox(x,y,z)= (1 + 1/4√3)/2 * min((1 / √3)*(|x|+|y|+|z|), max (|x|, |y|, |z|)) http://i53.tinypic.com/2vlphz8.gif

最大误差约为16%。

然而,应该指出的是,通常距离仅用于比较目的。例如,在经典的mandelbrot集(z←z2 + c)计算中,复数的大小通常与边界半径长度2进行比较。在这些情况下,可以简单地通过基本平方来减小平方根。比较的两面(因为距离总是非负的)。也就是说:

    √(Δx2+Δy2) < d is equivalent to Δx2+Δy2 < d2, if d ≥ 0

我还应该提到Richard G. Lyons的“理解数字信号处理”第13.2章有一个令人难以置信的2D距离算法集合(a.k.a复数幅度近似)。举个例子:

  

Max = x&gt;你? x:y;

     

Min = x&lt;你? x:y;

     

if(Min <0.04142135Max)

|V| = 0.99 * Max + 0.197 * Min;
     

否则

|V| = 0.84 * Max + 0.561 * Min;

与实际距离的最大误差为1.0%。惩罚当然是你做了几个分支;但即使是这个问题的“最被接受”的答案也至少有三个分支。

如果您真的想要对特定精度进行超快距离估计,可以使用与编译器供应商相同的基本方法编写自己的简化fsqrt()估计值,但精度较低,通过执行固定数量的迭代。例如,您可以消除极小或大数字的特殊情况处理,和/或减少Newton-Rapheson迭代次数。这是所谓的“Quake 3”fast inverse square root实现的关键策略 - 它是经典的Newton算法,只有一次迭代。

不要假设你的fsqrt()实现很慢而没有对它进行基准测试和/或读取源代码。大多数现代fsqrt()库实现都是无分支的,并且非常快。 Here for example is an old IBM floating point fsqrt implementation. Premature optimization是,而且永远是所有邪恶的根源。

答案 4 :(得分:6)

请注意,对于2(非负)距离AB,如果sqrt(A) < sqrt(B),那么A&lt; B。创建一个不会调用sqrt()的Get3DDistance()GetSqrOf3DDistance())的专用版本,该版本仅用于sortfunc()

答案 5 :(得分:4)

如果您担心表现,您还应该注意发送论据的方式:

float Get3dDistance( Vec3 c1, Vec3 c2 );

意味着两个Vec3结构的副本。改为使用引用:

float Get3dDistance( Vec3 const & c1, Vec3 const & c2 );

答案 6 :(得分:3)

您可以比较距离的平方而不是实际距离,因为d 2 =(x 1 -x 2 2 +(Y <子> 1 -y <子> 2 2 +(Z <子> 1 -z <子> 2 2 。它没有摆脱乘法,但确实消除了平方根运算。

答案 7 :(得分:1)

输入向量的更新频率和排序频率是多少?根据您的设计,将“Vec3”类扩展为预先计算的距离并对其进行排序可能非常有效。如果您的实现允许您使用矢量化操作,则尤为相关。

除此之外,请参阅flipcode.com article on approximating distance functions以了解另一种方法。

答案 8 :(得分:1)

稍微取决于您用来与之比较的点​​数,以下内容几乎可以肯定是按照近似顺序获取点列表,假设所有点在所有迭代中都发生变化。

1)将数组重写为单个曼哈顿距离列表 out [i] = abs(posn [i] .x - player.x)+ abs(posn [i] .y - player.y)+ abs(posn [i] .z - player.z);

2)现在,您可以对浮点数使用基数排序来对它们进行排序。

请注意,在实践中,这比排序3d位置列表要快得多,因为它显着降低了排序操作中的内存带宽要求,而这些操作始终是花费的,并且不可预测的访问和写作将会发生。这将在O(N)时间运行。

如果许多点在每个方向上都是静止的,那么使用KD-Tree的算法要快得多,尽管实现起来要复杂得多,并且要获得良好的内存访问模式要困难得多。

答案 9 :(得分:0)

如果这只是排序的值,那么您可以将sqrt()替换为abs()。如果需要将距离与设定值进行比较,请获取该值的平方值。

E.g。而不是针对a检查sqrt(...),你可以将abs(...)与a * a进行比较。

答案 10 :(得分:0)

您可能需要在计算时考虑缓存播放器与对象之间的距离,然后在sortfunc中使用它。这取决于您的排序函数查看每个对象的次数,因此您可能需要进行配置以确定。

我得到的是你的排序功能可能会做这样的事情:

compare(a,b);
compare(a,c);
compare(a,d);

你可以计算每次玩家和'a'之间的距离。

正如其他人所提到的,在这种情况下你可以省略sqrt

答案 11 :(得分:0)

如果您可以围绕播放器居中坐标,请使用球面坐标?然后你可以按半径排序。

虽然这是一个很大的问题。

答案 12 :(得分:0)

如果您的操作发生了很多,可能值得将其放入某些3D数据结构中。您可能需要距离排序来确定哪个对象可见,或者某些类似的任务。按照复杂程度,您可以使用:

  1. 统一(立方)细分

    将已用空间划分为单元格,并将对象分配给单元格。快速访问元素,邻居是微不足道的,但空单元格会占用大量空间。

  2. 四叉树

    给定一个阈值,将已用空间递归地划分为四个四边形,直到少于阈值数量的对象在内部。对数访问元素,如果对象不相互堆叠,邻居不难找到,节省空间的解决方案。

  3. 八叉树

    与Quadtree相同,但分为8,即使对象在彼此之上也是最佳的。

  4. Kd树

    给定一些启发式成本函数和阈值,将空间分成两半,其中一个平面的成本函数最小。 (例如:每侧有相同数量的物体。)递归重复,直到达到阈值。始终是对数的,邻居更难获得,节省空间(并且适用于所有维度)。

  5. 使用上述任何一种数据结构,您可以从一个位置开始,从邻居到邻居,以越来越远的距离列出对象。您可以停在所需的切割距离。您也可以跳过相机无法看到的单元格。

    对于距离检查,您可以执行上述任何一个例程,但最终它们不会随着对象数量的增加而扩展。这些可用于显示占用数百GB硬盘空间的数据。