Построение квадродерева, при котором существует только одна разница уровней между соседними узлами (LOD)

Я пытаюсь построить дерево квадрантов, которое подразделяет область на основе положения и максимальной глубины. Я хочу использовать это, чтобы реализовать уровень детализации ландшафта. Другими словами, у меня есть позиция (x, y), область (x, y, ширина), и я передаю ее некоторой сборке метода (region, position, maxDepth), которая затем должна возвращать массив узлов, которые покрывают весь самолет.

Моя реализация немного отличается от этой тем, что глубина, а корневая область представлена ​​объектом Quadtree. Чтобы получить общее подразделение, затем вызывается метод-член get (x, y, radius), который затем возвращает массив узлов, покрывающих всю корневую область (проверьте код внизу).

Чтобы избежать появления артефактов, для меня важно, чтобы между соседними узлами был максимум 1 уровень.

Ниже приведен пример приемлемого результата. Самая большая разница между соседними узлами - 1. (диагональные линии можно не учитывать, они просто результат триангуляции)

«Пример

С другой стороны, это неприемлемо, потому что между тремя соседними узлами существует разница в 2 единицы.

«Пример

Чтобы исправить это, нам нужно разделить соседние узлы следующим образом:

«Пример

Другой пример приемлемого решения:

«Пример

Это код, который у меня есть на данный момент.

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 следующим образом:

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

Если это полезно, узлы Quadtree также хранят указатель на своих родителей.

Я иду в неправильном направлении? Обеспечение ограничений путем возврата вверх по дереву и отслеживания позиций и прочего кажется мне слишком сложным. Есть ли здесь другой ракурс?


person crazymage    schedule 04.11.2017    source источник
comment
Пожалуйста, добавьте больше своего кода, чтобы было более понятно, какая у вас структура данных. Кроме того, вы, похоже, не используете понятие глубины в включенном фрагменте кода.   -  person trincot    schedule 05.11.2017
comment
Вы уверены, что между соседями возможна только одна разница уровней? Я думаю, что если у вас есть небольшие и плотные кластеры «данных», которые находятся далеко, это может быть невозможно. Вам нужно будет либо вставить пустые узлы в дерево, либо вставить «поддельные» точки данных, чтобы повысить уровень детализации вокруг плотных точечных кластеров ...   -  person TilmannZ    schedule 05.11.2017
comment
По запросу я добавил еще код и лучшие примеры.   -  person crazymage    schedule 05.11.2017
comment
pinging @Mario, чтобы открыть диалог о награде. Вы получили этот пинг?   -  person Guy Coder    schedule 01.01.2020
comment
Интересно: Как отправить сообщение благотворителю?   -  person Guy Coder    schedule 01.01.2020
comment
Попробуйте следующее: перед разделением четырехугольника проверьте его соседей в том же направлении, что и вектор координат игрока из центра четырехугольника, имеют ли они уже тот же размер, что и текущий четырехугольник. Если нет, сначала разделите их, а затем разделите текущий четырехугольник.   -  person Alenprintf    schedule 05.01.2020
comment
Короткий ответ: это невозможно. (Даже для простых одномерных двоичных деревьев это невозможно)   -  person wildplasser    schedule 05.01.2020
comment
@wildplasser ответ ниже показывает, как это сделать, чтобы никакие 2 соседние узлы не находились на расстоянии более 2 уровней.   -  person Anatolii    schedule 05.01.2020


Ответы (1)


Можно гарантировать, что никакие два соседних узла не будут удалены более чем на два уровня друг от друга, лишь с небольшой модификацией алгоритма. Идея состоит в том, чтобы разделить узел, когда есть пересечение между кругом и определенным прямоугольником, размеры которого зависят от квадрата узла, а также его глубины.

Подумайте, что влияет на необходимость разделения узла на заданной глубине, начиная с самого глубокого уровня вверх.

  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. Применяя аналогичные рассуждения к следующему уровню, узел (x, y, x + width, y + width) глубины maxDepth - 3 должен быть разделен, если есть пересечение между кругом, квадратным корнем и квадратом (x - width*3/4, y - width*3/4, x + width*5/2, y + width*5/2).

  5. Наконец, обобщая это на узел произвольной глубины, узел (x, y, x + width, y + width) должен быть разделен тогда и только тогда, когда есть пересечение между кругом, квадратным корнем и квадратом (x - width*inflationRatio/2, y - inflationRatio/2, x + width*(1+inflationRatio), y + width*(1+inflationRatio)), где inflationRatio = 2^(2-maxDepth+depth). (Это можно доказать по индукции, где каждый раздутый квадрат равен объединению узла и всех соседних раздутых квадратов на один уровень глубже).

введите здесь описание изображения

person Anton    schedule 05.01.2020