193 lines
6.9 KiB
JavaScript
193 lines
6.9 KiB
JavaScript
class Quadtree {
|
|
constructor(boundary, capacity) {
|
|
// Boundary is the bounding box of the area this quadtree node covers
|
|
// Capacity is the maximum number of curves a node can hold before subdividing
|
|
this.boundary = boundary; // {x: {min: <value>, max: <value>}, y: {min: <value>, max: <value>}}
|
|
this.capacity = capacity;
|
|
this.curveIndexes = [];
|
|
this.curves = [];
|
|
this.divided = false;
|
|
|
|
this.nw = null; // Northwest quadrant
|
|
this.ne = null; // Northeast quadrant
|
|
this.sw = null; // Southwest quadrant
|
|
this.se = null; // Southeast quadrant
|
|
}
|
|
|
|
|
|
// Check if a bounding box intersects with the boundary of this quadtree node
|
|
intersects(bbox) {
|
|
return !(bbox.x.max < this.boundary.x.min || bbox.x.min > this.boundary.x.max ||
|
|
bbox.y.max < this.boundary.y.min || bbox.y.min > this.boundary.y.max);
|
|
}
|
|
|
|
// Subdivide this quadtree node into 4 quadrants
|
|
subdivide() {
|
|
const xMid = (this.boundary.x.min + this.boundary.x.max) / 2;
|
|
const yMid = (this.boundary.y.min + this.boundary.y.max) / 2;
|
|
|
|
const nwBoundary = { x: { min: this.boundary.x.min, max: xMid }, y: { min: this.boundary.y.min, max: yMid }};
|
|
const neBoundary = { x: { min: xMid, max: this.boundary.x.max }, y: { min: this.boundary.y.min, max: yMid }};
|
|
const swBoundary = { x: { min: this.boundary.x.min, max: xMid }, y: { min: yMid, max: this.boundary.y.max }};
|
|
const seBoundary = { x: { min: xMid, max: this.boundary.x.max }, y: { min: yMid, max: this.boundary.y.max }};
|
|
|
|
this.nw = new Quadtree(nwBoundary, this.capacity);
|
|
this.ne = new Quadtree(neBoundary, this.capacity);
|
|
this.sw = new Quadtree(swBoundary, this.capacity);
|
|
this.se = new Quadtree(seBoundary, this.capacity);
|
|
|
|
this.divided = true;
|
|
}
|
|
|
|
insert (curve, curveIdx) {
|
|
const bbox = curve.bbox()
|
|
const isOutside = bbox.x.min < this.boundary.x.min ||
|
|
bbox.x.max > this.boundary.x.max ||
|
|
bbox.y.min < this.boundary.y.min ||
|
|
bbox.y.max > this.boundary.y.max;
|
|
if (isOutside) {
|
|
let newNode = new Quadtree(this.boundary, this.capacity)
|
|
newNode.curveIndexes = this.curveIndexes;
|
|
newNode.curves = this.curves;
|
|
newNode.divided = this.divided;
|
|
|
|
newNode.nw = this.nw;
|
|
newNode.ne = this.ne;
|
|
newNode.sw = this.sw;
|
|
newNode.se = this.se;
|
|
|
|
this.curveIndexes = [];
|
|
this.curves = [];
|
|
this.subdivide()
|
|
if (bbox.x.max < this.boundary.x.max) {
|
|
if (bbox.y.max < this.boundary.y.max) {
|
|
this.boundary.x.min -= this.boundary.x.max - this.boundary.x.min
|
|
this.boundary.y.min -= this.boundary.y.max - this.boundary.y.min
|
|
this.nw = newNode
|
|
} else {
|
|
this.boundary.x.min -= this.boundary.x.max - this.boundary.x.min
|
|
this.boundary.y.max += this.boundary.y.max - this.boundary.y.min
|
|
this.sw = newNode
|
|
}
|
|
} else {
|
|
if (bbox.y.max < this.boundary.y.max) {
|
|
this.boundary.x.max += this.boundary.x.max - this.boundary.x.min
|
|
this.boundary.y.min -= this.boundary.y.max - this.boundary.y.min
|
|
this.ne = newNode
|
|
} else {
|
|
this.boundary.x.max += this.boundary.x.max - this.boundary.x.min
|
|
this.boundary.y.max += this.boundary.y.max - this.boundary.y.min
|
|
this.se = newNode
|
|
}
|
|
}
|
|
return this.insert(curve, curveIdx)
|
|
} else {
|
|
return this._insert(curve, curveIdx)
|
|
}
|
|
}
|
|
|
|
// Insert a curve into the quadtree, subdividing if necessary
|
|
_insert(curve, curveIdx) {
|
|
if (curve.points[0].x==381.703125) {
|
|
console.log(this.intersects(curve.bbox()))
|
|
console.log(this.boundary)
|
|
}
|
|
// If the curve's bounding box doesn't intersect this node's boundary, do nothing
|
|
if (!this.intersects(curve.bbox())) {
|
|
return false;
|
|
}
|
|
|
|
// If the node has space, insert the curve here
|
|
if (this.curves.length < this.capacity) {
|
|
|
|
if (curve.points[0].x==381.703125) {
|
|
"inserting"
|
|
}
|
|
this.curves.push(curve);
|
|
this.curveIndexes.push(curveIdx)
|
|
return true;
|
|
}
|
|
|
|
if (curve.points[0].x==381.703125) {
|
|
console.log("going down a level")
|
|
}
|
|
|
|
// Otherwise, subdivide and insert the curve into the appropriate quadrant
|
|
if (!this.divided) {
|
|
this.subdivide();
|
|
}
|
|
|
|
const resultNw = this.nw._insert(curve, curveIdx);
|
|
const resultNe = this.ne._insert(curve, curveIdx);
|
|
const resultSw = this.sw._insert(curve, curveIdx);
|
|
const resultSe = this.se._insert(curve, curveIdx);
|
|
|
|
// Return true if any of the insert operations returned true
|
|
return resultNw || resultNe || resultSw || resultSe;
|
|
}
|
|
|
|
// Query all curves that intersect with a given bounding box
|
|
query(range, found = []) {
|
|
// If the range doesn't intersect with this node's boundary, return
|
|
if (!this.intersects(range)) {
|
|
return found;
|
|
}
|
|
|
|
// Check the curves in this node
|
|
for (let i = 0; i < this.curves.length; i++) {
|
|
if (this.bboxIntersect(this.curves[i].bbox(), range)) {
|
|
found.push(this.curveIndexes[i]); // Return the curve index instead of the curve
|
|
}
|
|
}
|
|
|
|
// If the node is subdivided, check the child quadrants
|
|
if (this.divided) {
|
|
this.nw.query(range, found);
|
|
this.ne.query(range, found);
|
|
this.sw.query(range, found);
|
|
this.se.query(range, found);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
// Helper method to check if two bounding boxes intersect
|
|
bboxIntersect(bbox1, bbox2) {
|
|
return !(bbox1.x.max < bbox2.x.min || bbox1.x.min > bbox2.x.max ||
|
|
bbox1.y.max < bbox2.y.min || bbox1.y.min > bbox2.y.max);
|
|
}
|
|
|
|
clear() {
|
|
this.curveIndexes = [];
|
|
this.curves = [];
|
|
this.divided = false;
|
|
|
|
this.nw = null; // Northwest quadrant
|
|
this.ne = null; // Northeast quadrant
|
|
this.sw = null; // Southwest quadrant
|
|
this.se = null; // Southeast quadrant
|
|
}
|
|
draw(ctx) {
|
|
// Debug visualization
|
|
ctx.save()
|
|
ctx.strokeStyle = "red"
|
|
ctx.lineWidth = 1
|
|
ctx.beginPath()
|
|
ctx.rect(
|
|
this.boundary.x.min,
|
|
this.boundary.y.min,
|
|
this.boundary.x.max-this.boundary.x.min,
|
|
this.boundary.y.max-this.boundary.y.min
|
|
)
|
|
ctx.stroke()
|
|
if (this.divided) {
|
|
this.nw.draw(ctx)
|
|
this.ne.draw(ctx)
|
|
this.sw.draw(ctx)
|
|
this.se.draw(ctx)
|
|
}
|
|
ctx.restore()
|
|
}
|
|
}
|
|
|
|
export { Quadtree }; |