10m node viewer w/ quadtree

This commit is contained in:
Oxy8
2026-02-08 01:00:12 -03:00
commit d6d37d93d5
11 changed files with 3385 additions and 0 deletions

113
src/quadtree.ts Normal file
View 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 };
}