构造四叉树,使相邻节点之间只有一个级别差异(LOD)

时间:2017-11-04 20:54:35

标签: algorithm data-structures tree quadtree level-of-detail

我正在尝试构建一个四叉树,它根据位置和最大深度细分区域。我想用它来实现地形的细节水平。换句话说,我有一个位置(x,y),一个区域(x,y,宽度),我将它传递给某个方法构建(区域,位置,maxDepth),然后应该返回一个覆盖整个飞机。

我的实现与此略有不同,因为深度和根区域由四叉树对象表示。要获得总细分,然后调用成员方法get(x,y,radius),然后返回覆盖整个根区域的节点数组(检查底部的代码)。

为了避免产生伪影,对我来说重要的是相邻节点之间最多有1个级别。

以下是可接受结果的示例。相邻节点之间的最大区别是1.(您可以忽略对角线,它们只是三角测量的结果)

Example 1: Correct

另一方面,这是不可接受的,因为三个相邻节点之间存在2的差异。

Example 2: Incorrect

要解决这个问题,我们必须像这样拆分相邻的节点:

Example 3: Corrected

可接受的解决方案的另一个例子是:

Example 4: Correct

这是我现在的代码。

class Quadtree {

    constructor({ x, y, width }, levels = 6, parent = null) {

        this.x = x;
        this.y = y;
        this.width = width;

        this.parent = parent;
        this.children = null;

        if (levels > 0) {
            this.children = this.constructor.split(this, levels); // recursively split quadtree.
        }
    }

    /**
     * Checks for intersection.
     * @param  {x, y, radius} circle
     * @param  {x, y, width} square
     * @return {boolean}
     */
    static intersects(circle, square) {
        let deltaX = circle.x - Math.max(square.x, Math.min(circle.x, square.x + square.width));
        let deltaY = circle.y - Math.max(square.y, Math.min(circle.y, square.y + square.width));

        return (deltaX * deltaX + deltaY * deltaY) < (circle.radius * circle.radius);
    }

    /**
     * Splits a node.
     */
    static split(node, levels) {
        let width = node.width / 2;
        let x = node.x;
        let y = node.y;

        // bottom left
        let q1 = new Quadtree({
            x: x,
            y: y,
            width
        }, levels - 1, node);

        // bottom right
        let q2 = new Quadtree({
            x: x + width,
            y: y,
            width
        }, levels - 1, node);

        // top left
        let q3 = new Quadtree({
            x: x,
            y: y + width,
            width
        }, levels - 1, node);

        // top right
        let q4 = new Quadtree({
            x: x + width,
            y: y + width,
            width
        }, levels - 1, node);

        return [q1, q2, q3, q4];
    }

    /**
     * Gets the least amount of nodes covered by the given circle.
     * @param  {x, y, radius} circle
     * @return {Array} An array of Quadtree-nodes.
     */
    get(circle) {

        if (this.children !== null && this.constructor.intersects(circle, { x: this.x, y: this.y, width: this.width })) { // we need to go deeper.
            return this.children.reduce((arr, child) => {

                return arr.concat(child.get(circle));

            }, []);

        } else {
            return [ this ];
        }
    }
}

以下是我将如何使用它的示例:

let tree = new Quadtree({ x: 0, y: 0, width: 100}, 2);
let nodes = tree.get({x: 15, y: 15, radius: 5}); // returns an array of nodes covering the whole region.

示例:

tree.get({x: -15, y: -15, radius: 5});
[ Quadtree { x: 0, y: 0, width: 100 } ] // returns the top node.

tree.get({x: 15, y: 15, radius: 5});
[ 7 Quadtree-nodes ]

最后一个示例返回七个四叉树节点,如下所示:

#-------#-------#
|       |       |
|       |       |
|       |       |
#---#---#-------#
|   |   |       |
#---#---|       |
|   |   |       |
#---#---#-------#

如果它有用,Quadtree节点也会存储指向其父节点的指针。

我是否朝错误的方向前进?通过返回树来执行约束,并跟踪位置和不存在的位置,对我来说似乎过于复杂。这里有不同的角度吗?

1 个答案:

答案 0 :(得分:2)

仅对算法进行少量修改,就可以确保两个相邻节点之间的距离不超过两个级别。想法是在圆与某个矩形的交点处分割节点,该矩形的尺寸取决于节点的正方形及其深度。

考虑什么因素会影响从给定深度的节点到最深层次的节点是否需要分割。

  1. 最大深度的节点无法拆分。

  2. 深度maxDepth - 1的节点仅在与圆相交时才应拆分。

  3. 深度为maxDepth - 2的节点与圆相交或与深度为maxDepth的节点相邻时,应将其拆分(因此,使其保持未拆分状态将违反要求)。但是,后一个条件等效于与深度为maxDepth - 1的已拆分节点相邻。反过来,这等效于使深度为maxDepth - 1的相邻节点与圆相交(请参见上一段)。我们如何确定是否没有遍历相邻节点并回溯的情况?请注意,当前节点(x, y, x + width, y + width)及其所有相邻节点的并集更深一层等于正方形(x - width/2, y - width/2, x + width*2, y+width*2)与根平方的交集。因此,整个条件归结为找到圆,根平方和当前膨胀为其大小两倍的正方形之间的交点。 (查看图片。)

  4. 对下一层应用类似的推理,如果圆,根平方和平方{{1之间存在相交点],则深度(x, y, x + width, y + width)的节点maxDepth - 3应该被分割}}。

  5. 最后,将其推广到任意深度的节点,并且仅当圆,根平方和平方{{1}之间存在交集时,才应拆分节点(x - width*3/4, y - width*3/4, x + width*5/2, y + width*5/2) },其中(x, y, x + width, y + width)。 (这可以通过归纳法来证明,其中每个膨胀的平方等于节点的联合,而所有相邻的膨胀的平方更深一层)。

enter image description here