I'm trying to build a quadtree which subdivides a region based on a position and a maximum depth. I want to use this to implement level of detail in terrain. In other words I have a position (x, y), a region (x, y, width), and I pass it to some method build(region, position, maxDepth), which should then return an array of nodes which cover the entire plane.
My implementation differs slightly from this in that the depth, and the root region is represented by a Quadtree-object. To get the total subdivision one then calls the member method get(x, y, radius), which then returns an array of nodes which cover the entire root region (check the code at the bottom).
To avoid getting artifacts it is important for me that there is a maximum of 1 level between adjacent nodes.
Below is an example of an acceptable result. The biggest difference between adjacent nodes is 1. (You can disregard the diagonal lines, they're just a result of triangulation)
This on the other hand is not acceptable because there is a difference of 2 between three adjacent nodes.
To fix this we will have to split the adjacent nodes like this:
Another example of an acceptable solution would be this:
This is the code I have as of now.
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 ];
}
}
}
Here's an example of how I would use it:
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.
Examples:
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 ]
The last example returns seven Quadtree-nodes like this:
#-------#-------#
| | |
| | |
| | |
#---#---#-------#
| | | |
#---#---| |
| | | |
#---#---#-------#
If it's useful the Quadtree-nodes also store a pointer to their parents.
Am I going at this in the wrong direction? Enforcing the constraints by going back up the tree, and keeping track of positions and what not, seems overly complicated to me. Is there a different angle here?