sortWorker.ts
examples/fractals/sortWorker.ts · raw · open the example · all examples · site home
Exports: PairingSortRequestSortResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
// Orders a table's samples for transitions (a module worker, no imports).
// In: a code per sample, x | y << 10 | z << 20 (10 bits each over the
// display ball), or 0xffffffff for a sample that draws nothing, and how to
// pair: "hilbert" (along a 3D Hilbert curve, so neighbors stay neighbors),
// "layers" (by height), "radial" (by distance from the center), "spiral"
// (by angle around the vertical) or "random". Out: the sample indices in
// that order, visible ones first, and how many are visible.
export type Pairing = "hilbert" | "layers" | "radial" | "spiral" | "random";
export interface SortRequest {
codes: Uint32Array;
pairing: Pairing;
id: number;
}
export interface SortResult {
order: Uint32Array;
visible: number;
id: number;
}
const BITS = 10;
const HIDDEN = 0xffffffff;
// Skilling's transpose of 3 axes of BITS bits into a Hilbert index
function hilbert(x: number, y: number, z: number) {
for (let q = 1 << (BITS - 1); q > 1; q >>= 1) {
const p = q - 1;
if (x & q) {
x ^= p;
}
if (y & q) {
x ^= p;
} else {
const t = (x ^ y) & p;
x ^= t;
y ^= t;
}
if (z & q) {
x ^= p;
} else {
const t = (x ^ z) & p;
x ^= t;
z ^= t;
}
}
y ^= x;
z ^= y;
let t = 0;
for (let q = 1 << (BITS - 1); q > 1; q >>= 1) {
if (z & q) {
t ^= q - 1;
}
}
x ^= t;
y ^= t;
z ^= t;
let h = 0;
for (let b = BITS - 1; b >= 0; b -= 1) {
h = (h << 3) | (((x >> b) & 1) << 2) | (((y >> b) & 1) << 1) | ((z >> b) & 1);
}
return h >>> 0;
}
function hash(v: number) {
const state = (Math.imul(v >>> 0, 747796405) + 2891336453) >>> 0;
const word = Math.imul(((state >>> ((state >>> 28) + 4)) ^ state) >>> 0, 277803737) >>> 0;
return ((word >>> 22) ^ word) >>> 0;
}
function keyOf(code: number, index: number, pairing: Pairing) {
const x = code & 1023;
const y = (code >>> 10) & 1023;
const z = (code >>> 20) & 1023;
if (pairing === "hilbert") {
return hilbert(x, y, z);
}
if (pairing === "layers") {
return ((y << 20) | (x << 10) | z) >>> 0;
}
const dx = x - 512;
const dz = z - 512;
const angle = Math.floor(((Math.atan2(dz, dx) / Math.PI + 1) / 2) * 1023.99);
if (pairing === "radial") {
const r = Math.min(1023, Math.floor(Math.sqrt(dx * dx + (y - 512) * (y - 512) + dz * dz) * 1.15));
return ((r << 20) | (angle << 10) | y) >>> 0;
}
if (pairing === "spiral") {
return ((angle << 20) | (y << 10) | Math.min(1023, Math.floor(Math.sqrt(dx * dx + dz * dz) * 1.4))) >>> 0;
}
return hash(index) >>> 2;
}
// LSD radix sort of (key, value) pairs, 11 bits a pass
function sortPairs(keys: Uint32Array<ArrayBuffer>, values: Uint32Array<ArrayBuffer>) {
const n = keys.length;
let k0 = keys;
let v0 = values;
let k1 = new Uint32Array(n);
let v1 = new Uint32Array(n);
const counts = new Uint32Array(2048);
for (let shift = 0; shift < 32; shift += 11) {
counts.fill(0);
for (let i = 0; i < n; i += 1) {
counts[(k0[i] >>> shift) & 2047] += 1;
}
let sum = 0;
for (let b = 0; b < 2048; b += 1) {
const c = counts[b];
counts[b] = sum;
sum += c;
}
for (let i = 0; i < n; i += 1) {
const slot = counts[(k0[i] >>> shift) & 2047]++;
k1[slot] = k0[i];
v1[slot] = v0[i];
}
[k0, k1] = [k1, k0];
[v0, v1] = [v1, v0];
}
return v0;
}
addEventListener("message", (event: MessageEvent<SortRequest>) => {
const { codes, pairing, id } = event.data;
const n = codes.length;
const keys = new Uint32Array(n);
const values = new Uint32Array(n);
let visible = 0;
for (let i = 0; i < n; i += 1) {
const code = codes[i];
keys[i] = code === HIDDEN ? HIDDEN : keyOf(code, i, pairing);
values[i] = i;
visible += code === HIDDEN ? 0 : 1;
}
const order = sortPairs(keys, values);
const result: SortResult = { order, visible, id };
(self as unknown as Worker).postMessage(result, [order.buffer]);
});