class BT {
constructor(node = null) {
this.node = node;
}
height() {
if (!this.node) return -1;
if (this.node.leaf()) return 0;
return Math.max(this.node.left.height() + 1, this.node.right.height() + 1);
}
*inorder() {
if (this.node) {
if (this.node.left) yield* this.node.left.inorder();
yield this.node;
if (this.node.right) yield* this.node.right.inorder();
}
}
*level(l) {
if (l == 0) yield this.node;
else {
if (this.node.left) yield* this.node.left.level(l - 1);
if (this.node.right) yield* this.node.right.level(l - 1);
}
}
*approximation(l) {
if (l == 0 || this.node.leaf()) yield this.node;
else {
if (this.node.left) yield* this.node.left.approximation(l - 1);
if (this.node.right) yield* this.node.right.approximation(l - 1);
}
}
// Generates all leaf nodes of this tree
*leafBalls() {
if (this.node.leaf()) yield this.node.entry;
else {
yield* this.node.left.leafBalls();
yield* this.node.right.leafBalls();
}
}
// Generates all leaf notes within distance d of point p
*within(p, d) {
let self = this;
self.withinSteps = 0;
function* traverse(node) {
let dnode = node.entry.dist(p);
self.withinSteps++;
if (dnode <= d) {
if (node.leaf()) yield node;
else {
let dleft = node.left.node.entry.dist(p);
let dright = node.right.node.entry.dist(p);
if (dleft < dright) {
yield* traverse(node.left.node);
yield* traverse(node.right.node);
} else {
yield* traverse(node.left.node);
yield* traverse(node.right.node);
}
}
}
}
if (this.node) yield* traverse(this.node);
}
// Reduces the bounding balls from internal nodes to those
// enclosing only the leaves
computeEnclosingCircles() {
if (!this.node.leaf()) {
this.node.entry = Ball.enclose([...this.leafBalls()]);
//Ball.from(d3.packEnclose([...this.leafBalls()]));
this.node.left.computeEnclosingCircles();
this.node.right.computeEnclosingCircles();
}
return this;
}
// Total area of the tree
area() {
if (this.node.leaf()) return this.node.entry.area;
return (
this.node.entry.area + this.node.left.area() + this.node.right.area()
);
}
// Draws all balls using context ctx
draw(ctx) {
for (let node of this.inorder()) {
node.entry.draw(ctx);
}
}
// Finds a leaf matching position and radius with the given ball
// returns a pair {node, ancestors} where node is
// the leaf node with the found ball, and ancestor is an array of
// tree objects [T0, T1, ... Tn], such that Ti+1 is the parent of Ti,
// Tn is the tree containing node
find(ball) {
this.findSteps = 0;
this.visited = [];
let traverse = (ancestors) => {
let n = ancestors.length;
let node = ancestors[n - 1].node;
this.visited.push(node);
this.findSteps++;
if (node.leaf()) {
if (node.entry.equals(ball)) return { node, ancestors };
return null;
}
let result = null;
if (node.entry.containsPoint(ball)) {
let dleft = node.left.node.entry.dist(ball);
let dright = node.right.node.entry.dist(ball);
if (dleft < dright) {
ancestors.push(node.left);
result = traverse(ancestors);
if (result) return result;
ancestors.pop();
ancestors.push(node.right);
result = traverse(ancestors);
} else {
ancestors.push(node.right);
result = traverse(ancestors);
if (result) return result;
ancestors.pop();
ancestors.push(node.left);
result = traverse(ancestors);
}
if (result) return result;
ancestors.pop();
}
return null;
};
if (this.node) return traverse([this]);
}
// Returns a leaf ball containing p, or null, if none exists
intersecting(p) {
this.intersectingSteps = 0;
this.visited = [];
let traverse = (node) => {
this.intersectingSteps++;
this.visited.push(node);
if (node.leaf()) {
if (node.entry.containsPoint(p)) return node.entry;
return null;
}
if (!node.entry.containsPoint(p)) return null;
let dleft = node.left.node.entry.dist(p);
let dright = node.right.node.entry.dist(p);
if (dleft < dright) {
let result = traverse(node.left.node);
if (result) return result;
return traverse(node.right.node);
}
let result = traverse(node.right.node);
if (result) return result;
return traverse(node.left.node);
};
return traverse(this.node);
}
// Returns a list of balls enclosed in the given ball
findInCircle(ball) {
this.findInCircleSteps = 0;
this.visited = [];
let result = [];
let traverse = (node) => {
this.findInCircleSteps++;
this.visited.push(node);
if (node.leaf()) {
if (ball.encloses(node.entry)) result.push(node.entry);
} else {
if (node.left.node.entry.intersects(ball)) traverse(node.left.node);
if (node.right.node.entry.intersects(ball)) traverse(node.right.node);
}
};
if (this.node.entry.intersects(ball)) traverse(this.node);
return result;
}
// Returns the ball in a leaf node whose center is closest to point p.
// Uses branch and bound to speed up the search.
closest(p) {
let best = null;
let bestDist = Number.POSITIVE_INFINITY;
this.closestSteps = 0;
this.visited = [];
let traverse = (node) => {
if (bestDist == 0) return;
this.closestSteps++;
this.visited.push(node);
if (node.leaf()) {
let d = node.entry.dist(p);
if (d < bestDist) [best, bestDist] = [node.entry, d];
return;
}
let dleft = node.left.node.entry.dist(p);
let dright = node.right.node.entry.dist(p);
if (dleft < dright) {
if (dleft < bestDist) traverse(node.left.node);
if (dright < bestDist) traverse(node.right.node);
} else {
if (dright < bestDist) traverse(node.right.node);
if (dleft < bestDist) traverse(node.left.node);
}
};
if (this.node) traverse(this.node);
return best;
}
// Returns a leaf node which would result in the smallest union with the given ball.
// Uses branch and bound to speed the search
smallestUnionLeaf(ball) {
let best = null;
let bestUnionArea = Number.POSITIVE_INFINITY;
this.smallestUnionSteps = 0;
this.visited = [];
let traverse = (node) => {
this.smallestUnionSteps++;
this.visited.push(node);
let expansion = ball.expand(node.entry).area;
if (expansion > bestUnionArea) return;
if (node.leaf()) {
let union = node.entry.union(ball);
let area = union.area;
if (area < bestUnionArea) {
best = node;
bestUnionArea = area;
}
return;
}
let eleft = ball.expand(node.left.node.entry);
let eright = ball.expand(node.right.node.entry);
if (eleft.area < eright.area) {
if (eleft.area < bestUnionArea) traverse(node.left.node);
if (eright.area < bestUnionArea) traverse(node.right.node);
} else {
if (eright.area < bestUnionArea) traverse(node.right.node);
if (eleft.area < bestUnionArea) traverse(node.left.node);
}
};
if (this.node) traverse(this.node);
return best;
}
}