形状(矩形)如何在四叉树中工作?


10

有人告诉我,四叉树是我的游戏的理想数据结构,但是我在理解形状在四叉树中的工作方式时遇到了麻烦。

我正在用JavaScript进行此操作,但我认为这些问题可能适用于任何语言的四叉树。

我想我最了解基本(x,y)点插入在四叉树中的工作原理,并且我可以在纸上做到这一点。

这是我尝试点的JSfiddle

四叉树点

除了一个案例,我的分数测试按预期进行。

但是,当涉及矩形等形状时,我的困惑就开始了。当从具有形状的四叉树中检索时,它会检查形状的每个点以及它们落入哪些节点?当形状插入接受每个形状的(x,y,width,height)参数时,形状插入甚至如何工作?它是否使用起点的宽度/高度来计算其他角点,然后将其分配到适当的节点中?如果插入的形状跨越四个节点,该形状的数据是否保存到所有四个节点中?

当检索方法接受形状作为参数(x,y,width,height)时,实际上是怎么回事?是否首先查看形状要插入到哪个节点,然后检索这些节点的所有对象?

我有一个使用shape的JSfiddle,但是我对测试结果完全感到困惑。我正在收到重复的对象!

四叉树形状

例如,红色方块等效于我输入到检索方法中的参数。我认为,由于这个红色正方形跨越了所有四个节点,因此它应该返回四叉树中的每个对象!但事实并非如此,而且我在合理化返回结果方面遇到了麻烦。目前,我还有许多其他测试已被注释掉,但是您可以取消注释并运行它们以查看更令人困惑的结果!

就像说,如果我想返回四叉树中的所有点,我该怎么做?使用形状的整个边界范围的检索方法?例如,retrieve(0,0,canvas.width,canvas.height)?

我正在使用的JavaScript QuadTree库已被其他各种来源引用,因此,我认为实际的实现是正确且信誉良好的。

我认为我的许多困惑可能源于对四叉树术语的误解。就像,为什么当“点”也具有width / height参数时,为什么用边界而不是尺寸?是约定俗成的问题还是它们的完全不同的概念?

谢谢你的时间!


它们按其位置正常存储在四叉树中。通常,它们在中心,但可以在定义的角落。您的问题可能是该问题的重复部分:QuadTree:仅存储点还是区域?
MichaelHouse

我已经看过这个问题,但是我仍然不完全理解答案:(。“即使超过容量,您也必须将其存储在完全包含它的最小节点中(使用可调整大小的容器)。”-当他说“完全”包含它的最小节点,如果对象很大,那么该节点通常不是非叶子节点吗?就像在仅由其他节点组成的上层节点中那样?我,因为这将意味着该含节点将具有1个叶和4个小的节点在其内。
user2736286

1
@ user2736286如果查看四叉树库的代码(它不是很长),您会看到它确实在更高级别的节点中存储了矩形,以防止它们跨越节点边界。请参阅_stuckChildren代码中对该字段的引用。您还可以在“带边界检索项目”示例中看到这一点-它始终突出显示红色的节点,这些节点跨越了您单击的节点的边界,一直延伸到根节点。
内森·里德

@ user2736286此外,四叉树库似乎在检索带边界项时存在错误-如果您给它提供跨越某些节点边界的查询rect,则它不会返回查询接触的节点中的所有rect。通过单击节点边界附近,您也可以在“带边界检索项目”中轻松看到这一点。
内森·里德

@NathanReed之前,我试图理解代码,但是如果没有概念性的基础,我不足以理解它。多亏了约翰·麦克唐纳(John McDonald)的伪代码,我现在了解了如何将矩形插入节点中,但是我仍然不清楚检索的工作方式。至于“有边界的检索项目”-我对这个例子感到困惑。就像,当我单击完全适合最小节点之一的rect时,为什么还突出显示该节点之外的所有其他rect?
user2736286 2013年

Answers:


10

四叉树通常存储和检索矩形。点是宽度和高度为零的特定情况。以下逻辑用于从根节点开始在树中查找新矩形的位置:

void Store(Rectangle rect)
{
    if(I have children nodes)
    {
        bool storedInChild = false;
        foreach(Node childNode in nodes)
        {
            if(this rectangle fits entirely inside the childNode bounds)
            {
                childNode.Store(rect);   // Go deeper into the tree
                storedInChild = true;
                break;
            }
        }
        if(not storedInChild)
        {
            Add this rectangle to the current node
        }
    }
    else
    {
        Add this rectangle to the current node
    }
}

请注意,矩形可以存储在任何深度,而不必是叶子四边形。如果矩形在根级别跨越边界,则四边形将存储该矩形。

例如,红色方块等效于我输入到检索方法中的参数。我认为,由于这个红色正方形跨越了所有四个节点,因此它应该返回四叉树中的每个对象!但事实并非如此,而且我在合理化返回结果方面遇到了麻烦。

查询四叉树时,它将仅返回查询所包含的矩形。在您的示例中,您将强制详细查看4个子四边形中的每一个,因为红色查询矩形与每个子四边形相交。由于子级没有其他子级,因此将这些子方格中的每个矩形与红色矩形进行比较。由于树中的矩形都不与红色查询矩形发生冲突,因此不应返回任何内容。

当您有很多对象,并且与查询区域相比,有很多空间可以容纳这些对象时,您才真正开始看到四叉树的好处。在这些情况下,较小的查询区域可以快速消除四叉树的大量块。只有与查询矩形相交的四边形才会被更详细地查看。

编辑

通常按照以下方式进行检索:

List<Rectangle> Query(Rectangle queryRect)
{
    List<Rectangle> results;
    foreach(Node childNode in children)
    {
        if(queryRect intersects with childNode bounds)
        {
            results += childNode.Query(queryRect);   // Dig deeper into the tree
        }
    }

    foreach(Rectangle I have stored in this quad)
    {
        if(queryRect intersects with rect)  // Your library doesn't do this
        {
            results += rect;
        }
    }

    return results;
}

但是,在您的库中,似乎没有检查返回的矩形是否与查询相交,因此您需要自己添加。


在更详细地查看了您的库之后,您的库将返回您指定的查询矩形的所有可能的碰撞候选者列表。似乎将您的查询矩形与每个候选项进行比较是您的工作,以查看是否存在实际冲突。大多数库为您完成最后一步,并且只返回与查询区域的实际冲突。因此,您所需要做的就是在retrieve方法中添加一些逻辑以修剪列表。你可以看看它都做了一些更清晰的位置:mikechambers.com/html5/javascript/QuadTree/examples/...
约翰·麦克唐纳

1
@ user2736286以防万一,您需要注意JohnMcDonald编写的Query和Store函数中的递归。如果您没有得到那部分,那就没有多大意义了。对于这两种方式,每次递归都深入到树中,即从树枝上退出,最后到叶子上。
TASagent

谢谢约翰,您的伪代码非常有帮助。当您说“ if(queryRect与childNode边界相交)”时,这是否基本上意味着queryRect是否包含在边界内(部分或全部)?只想被100%清除。另外,在讨论的“按边界检索项目”示例中,我具有单击结果的图像。 图片蓝点是我单击的地方。那么,为什么不只突出该节点内的两个矩形呢?为什么远离它的矩形也要突出显示?我认为那真的使我感到困惑。
user2736286 2013年

部分或全部。如果两者相互接触,或者其中一个完全被另一个完全包含。您想阅读Quadtrees上的Wiki ,但是我已经拍摄了您的照片,并对其进行了颜色编码以表示不同的子边界。所有蓝色矩形都与第一个子四边形的边界相交,然后洋红色深一层,绿色深一层。红色矩形与单击的四边形相交。您的库返回子边界上的所有rect
John McDonald

好的,所以我尽力查看了lib的源代码,终于了解了stickChildren的行为,而我照片中所有这些额外的rect都只是stickChildren。最初,我认为跨越多个节点的所有rect只会复制其数据并将其插入每个较小的节点中。现在我意识到,stickedChildren不会插入到它跨越的节点中,而只是停留在包含它的一个节点中,这就是为什么我还必须检查所有父节点,而不仅仅是检查包含我的查询rect的最小节点的原因。谢谢你的照片; 它现在变得更有意义了:)
user2736286
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.