10m node viewer w/ quadtree
This commit is contained in:
113
src/quadtree.ts
Normal file
113
src/quadtree.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Quadtree that spatially sorts a particle array in-place at build time.
|
||||
* Stores only leaf index ranges [start, end) into the sorted array.
|
||||
* NO per-frame methods — this is purely a build-time spatial index.
|
||||
*/
|
||||
|
||||
export interface Leaf {
|
||||
start: number;
|
||||
end: number;
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spatially sort particles using a quadtree and return
|
||||
* the sorted array + leaf ranges.
|
||||
*
|
||||
* Takes raw Float32Arrays (no object allocation).
|
||||
* Uses in-place partitioning (zero temporary arrays).
|
||||
*/
|
||||
export function buildSpatialIndex(
|
||||
xs: Float32Array,
|
||||
ys: Float32Array
|
||||
): { sorted: Float32Array; leaves: Leaf[] } {
|
||||
const n = xs.length;
|
||||
const order = new Uint32Array(n);
|
||||
for (let i = 0; i < n; i++) order[i] = i;
|
||||
|
||||
// Find bounds
|
||||
let minX = Infinity,
|
||||
minY = Infinity,
|
||||
maxX = -Infinity,
|
||||
maxY = -Infinity;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const x = xs[i], y = ys[i];
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
}
|
||||
|
||||
const leaves: Leaf[] = [];
|
||||
|
||||
// In-place quicksort-style partitioning
|
||||
function partition(
|
||||
vals: Float32Array,
|
||||
start: number,
|
||||
end: number,
|
||||
mid: number
|
||||
): number {
|
||||
let lo = start,
|
||||
hi = end - 1;
|
||||
while (lo <= hi) {
|
||||
while (lo <= hi && vals[order[lo]] < mid) lo++;
|
||||
while (lo <= hi && vals[order[hi]] >= mid) hi--;
|
||||
if (lo < hi) {
|
||||
const t = order[lo];
|
||||
order[lo] = order[hi];
|
||||
order[hi] = t;
|
||||
lo++;
|
||||
hi--;
|
||||
}
|
||||
}
|
||||
return lo;
|
||||
}
|
||||
|
||||
function build(
|
||||
start: number,
|
||||
end: number,
|
||||
bMinX: number,
|
||||
bMinY: number,
|
||||
bMaxX: number,
|
||||
bMaxY: number,
|
||||
depth: number
|
||||
): void {
|
||||
const count = end - start;
|
||||
if (count <= 0) return;
|
||||
|
||||
// Leaf: stop subdividing
|
||||
if (count <= 4096 || depth >= 12) {
|
||||
leaves.push({ start, end, minX: bMinX, minY: bMinY, maxX: bMaxX, maxY: bMaxY });
|
||||
return;
|
||||
}
|
||||
|
||||
const midX = (bMinX + bMaxX) / 2;
|
||||
const midY = (bMinY + bMaxY) / 2;
|
||||
|
||||
// Partition by X, then each half by Y
|
||||
const splitX = partition(xs, start, end, midX);
|
||||
const splitLeftY = partition(ys, start, splitX, midY);
|
||||
const splitRightY = partition(ys, splitX, end, midY);
|
||||
|
||||
// BL, TL, BR, TR
|
||||
build(start, splitLeftY, bMinX, bMinY, midX, midY, depth + 1);
|
||||
build(splitLeftY, splitX, bMinX, midY, midX, bMaxY, depth + 1);
|
||||
build(splitX, splitRightY, midX, bMinY, bMaxX, midY, depth + 1);
|
||||
build(splitRightY, end, midX, midY, bMaxX, bMaxY, depth + 1);
|
||||
}
|
||||
|
||||
build(0, n, minX, minY, maxX, maxY, 0);
|
||||
|
||||
// Reorder particles to match tree layout
|
||||
const sorted = new Float32Array(n * 2);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const src = order[i];
|
||||
sorted[i * 2] = xs[src];
|
||||
sorted[i * 2 + 1] = ys[src];
|
||||
}
|
||||
|
||||
return { sorted, leaves };
|
||||
}
|
||||
Reference in New Issue
Block a user