在2D中,如何有效地找到最接近点的对象?


35

我有一个相当大的游戏引擎,我想要一个功能来查找最接近的点列表。

我可以简单地使用勾股定理找到每个距离并选择最小距离,但这需要对所有距离进行迭代。

我也有一个碰撞系统,从本质上讲,我将对象变成较小网格上的较小对象(有点像小地图),并且只有在相同网格空间中存在对象时,我才检查碰撞。我可以这样做,仅增大网格间距以检查紧密度即可。(而不是检查每个对象。)但是,这将需要在我的基类中进行其他设置,并使本已混乱的对象杂乱无章。这值得么?

是否可以使用一些有效且准确的方法来根据点和大小的列表来检测哪个对象最接近?


存储x和y位置的平方形式,这样您就可以进行毕达哥拉斯定理,而不必在最后进行昂贵的sqrt。
乔纳森·康奈尔

3
这称为最近邻居搜索。互联网上有很多关于它的文章。通常的解决方案是使用某种空间分区树。
BlueRaja-Danny Pflughoeft 2011年

Answers:


38

最近邻搜索中的四边形 / 八叉树问题是,最近的对象可能正好位于节点之间的分界上。对于冲突,这是可以的,因为如果不在节点中,我们将不在乎。但是考虑一下带有四叉树的二维示例:

四叉树示例

在这里,即使黑色项目和绿色项目位于同一节点中,黑色项目也最接近蓝色项目。ultifinitus的答案只能保证最近的邻居,只有树中的每一项都放置在可能包含该树的最小可能节点中,或者放置在唯一节点中-这会导致效率更低的四叉树。(请注意,有许多不同的方法可以实现可称为四叉树/八叉树的结构-在此应用程序中,更严格的实现可能会更好。)

更好的选择是kd-tree。Kd树具有可以实现的非常有效的最近邻居搜索算法,并且可以包含任意数量的维(因此为“ k”维。)

来自Wikipedia的精彩而翔实的动画: kd-tree最近邻居搜索

如果我没记错的话,使用kd-tree的最大问题是,在保持平衡的同时,插入/删除kd-tree更加困难。因此,我建议为静态对象(例如房屋和树木)使用一棵kd树,该kd树高度平衡,而其中包含玩家和车辆的kd树则需要定期平衡。找到最近的静态对象和最近的移动对象,并比较这两个对象。

最后,kd-trees的实现相对简单,我相信您可以在其中找到大量的C ++库。据我所知,R树要复杂得多,如果您需要的只是一个简单的最近邻搜索,可能就算过头了。


1
很好的答案,小的细节“只能保证最近的邻居,只有树中的每个项目都放置在最小的可能节点中”,我的意思是对相同和相邻节点中的所有项目进行迭代,因此您遍历了10个而不是10.000。
罗伊(Roy T.)

1
非常正确-我认为“仅”是一个相当苛刻的词。肯定有几种方法可以将四叉树哄骗到最近邻居搜索中,具体取决于您如何实现它们,但是如果您由于其他原因(例如碰撞检测)尚未使用它们,我会坚持使用更优化的kd-tree。
dlras2'7

我想指出的是,我做了一个处理黑,绿,蓝问题的实现。检查底部。
clankill3r 2015年

18

sqrt() 对于非负参数是单调的或保留顺序的,因此:

sqrt(x) < sqrt(y) iff x < y

反之亦然。

因此,如果您只想比较两个距离,但对它们的实际值不感兴趣,则可以sqrt()从毕达哥拉斯中删去-step:

pseudoDistanceB = (A.x - B.x + (A.y - B.y
pseudoDistanceC = (A.x - C.x + (A.y - C.y
if (pseudoDistanceB < pseudoDistanceC)
{
    A is closest to B!
}
else
{
    A is closest to C!
}

它不像八叉树那么高效,但是它更容易实现,并且速度至少提高了一点


1
该度量标准也称为平方欧几里德距离
moooeeeep 2014年

10

您必须进行空间分区,在这种情况下,您需要创建有效的数据结构(通常是八叉树)。在这种情况下,每个对象都在一个或多个空格(立方体)中。如果您知道自己在哪个空格中,则可以查找O(1)哪个空格是您的邻居。

在这种情况下,可以通过首先迭代您自己空间中的所有对象以寻找最接近的对象来找到最接近的对象。如果没有人,则可以检查您的第一个邻居;如果没有人,则可以检查其邻居,等等。

这样,您可以轻松找到最近的对象,而不必遍历世界中的所有对象。像往常一样,这种速度提升确实需要一些记账,但是对于所有事物它确实有用,因此,如果您拥有广阔的世界,那么绝对值得实现空间分区和八叉树。

与往常一样,另请参阅维基百科文章:http//en.wikipedia.org/wiki/Octree


7
@ultifinitus要添加到此:如果您的游戏是2D,则可以使用QuadTrees代替Octrees。
TravisG 2011年


0

这是我的Java实现,用于从QuadTree中获取最接近的实现。它处理了dlras2描述的问题:

在此处输入图片说明

我认为手术真的很有效。它基于到四边形的距离,以避免比当前最接近的四边形搜索。

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

public T getClosest(float x, float y) {

    Closest closest = new Closest();
    getClosest(x, y, closest);

    return closest.item;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

protected void getClosest(float x, float y, Closest closestInfo) {


    if (hasQuads) {

        // we have no starting point yet
        // so get one
        if (closestInfo.item == null) {
            // check all 4 cause there could be a empty one
            for (int i = 0; i < 4; i++) {
                quads[i].getClosest(x, y, closestInfo);
                if (closestInfo.item != null) {
                    // now we have a starting point
                    getClosest(x, y, closestInfo);
                    return;
                }

            }
        }
        else {

            // we have a item set as closest
            // we should check if this quad is
            // closer then the current closest distance
            // let's start with the closest from index

            int closestIndex = getIndex(x, y);

            float d = quads[closestIndex].bounds.distToPointSQ(x, y);

            if (d < closestInfo.dist) {
                quads[closestIndex].getClosest(x, y, closestInfo);
            }

            // check the others
            for (int i = 0; i < 4; i++) {
                if (i == closestIndex) continue;

                d = quads[i].bounds.distToPointSQ(x, y);

                if (d < closestInfo.dist) {
                    quads[i].getClosest(x, y, closestInfo);
                }

            }

        }

    }
    else {

        for (int i = 0; i < items.size(); i++) {

            T item = items.get(i);

            float dist = distSQ(x, y, getXY.x(item), getXY.y(item));

            if (dist < closestInfo.dist) {
                closestInfo.dist = dist;
                closestInfo.item = item;
                closestInfo.tree = this;
            }

        }
    }

}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


class Closest {

    QuadTree<T> tree;
    T item;
    float dist = Float.MAX_VALUE;

}

ps我仍然认为使用kd-tree或其他方法更好,但这可能会对人们有所帮助。
clankill3r 2015年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.