class KDTree {
constructor(root = null) {
this.root = root;
}
toString() {
if (this.root) {
return `${this.root.point}`;
}
return "null";
}
insert(q, level = 0) {
if (this.root == null) {
this.root = new KDNode(
q,
levelToCutDim(level),
new KDTree(),
new KDTree(),
1
);
} else {
let { point, cutDim, left, right } = this.root;
if (q[cutDim] >= point[cutDim]) right.insert(q, level + 1);
else left.insert(q, level + 1);
this.root.size += 1;
}
}
findMin(i) {
if (this.root) {
let { point, cutDim, left, right } = this.root;
if (cutDim == i) {
if (left.root) return left.findMin(i);
return point;
} else {
if (left.root) {
let q = left.findMin(i);
if (q[i] < point[i]) point = q;
}
if (right.root) {
let q = right.findMin(i);
if (q[i] < point[i]) point = q;
}
return point;
}
}
}
// Deletes point q from the k-d-tree.
delete(q) {
if (!this.root) throw `not in tree: ${q}`;
this.root.size--;
let { point, cutDim, left, right } = this.root;
if (pointEquals(point, q)) {
// found point
if (!left.root && !right.root) {
this.root = null;
} else {
if (right.root) {
let r = right.findMin(cutDim);
right.delete(r);
//this.root.size++;
this.root.point = r;
} else {
let r = left.findMin(cutDim);
left.delete(r);
//this.root.size++;
this.root.point = r;
this.root.right.root = left.root;
this.root.left.root = null;
}
}
} else if (q[cutDim] < point[cutDim]) {
left.delete(q);
} else {
right.delete(q);
}
}
// Returns the number of points contained in rectangle r
// Side effect: all visited cells (rectangles) are stored in array 'visited'. Used for
// debugging and demonstration drawings
rangeCount(r, cell = world, visited = []) {
if (!this.root) return 0; // empty subtree
visited.push(cell);
if (r.isDisjointFrom(cell)) return 0; // no overlap with range
if (r.containsRectangle(cell))
// range contains our entire cell?
return this.root.size; // include all points in the count
// range partially overlaps cell
let count = 0;
if (r.containsPoint(this.root.point))
// consider this point
count++;
// apply recursively to children
count += this.root.left.rangeCount(
r,
cell.leftPart(this.root.cutDim, this.root.point),
visited
);
count += this.root.right.rangeCount(
r,
cell.rightPart(this.root.cutDim, this.root.point),
visited
);
return count;
}
// Returns an array with all points contained in rectangle r
rangeSelect(r, cell = world, points = []) {
if (!this.root) return points; // empty subtree
if (r.isDisjointFrom(cell)) return points; // no overlap with range
// range overlaps cell
if (r.containsPoint(this.root.point))
// consider this point
points.push(this.root.point);
// apply recursively to children
this.root.left.rangeSelect(
r,
cell.leftPart(this.root.cutDim, this.root.point),
points
);
this.root.right.rangeSelect(
r,
cell.rightPart(this.root.cutDim, this.root.point),
points
);
return points;
}
// Returns the distance to the point closest to q
// Side effect: all visited cells (rectangles) are stored in array 'visited'. Used for
// debugging and demonstration drawings
nearestNeighbor(q, cell = world, bestDist = Infinity, visited = []) {
if (this.root) {
let { point, cutDim, left, right } = this.root;
bestDist = Math.min(bestDist, pointDistance(point, q));
visited.push(cell);
let toVisit = [];
if (left.root) {
let rect = cell.leftPart(cutDim, point);
let dist = rect.distanceToPoint(q);
toVisit.push({ rect, dist, tree: left });
}
if (right.root) {
let rect = cell.rightPart(cutDim, point);
let dist = rect.distanceToPoint(q);
toVisit.push({ rect, dist, tree: right });
}
if (toVisit.length == 2 && toVisit[0].dist > toVisit[1].dist)
toVisit.reverse();
for (let { rect, dist, tree } of toVisit) {
if (dist < bestDist)
bestDist = Math.min(
bestDist,
tree.nearestNeighbor(q, rect, bestDist, visited)
);
}
}
return bestDist;
}
}