playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
242 lines (241 loc) • 8.13 kB
JavaScript
function SortWorker() {
const myself = typeof self !== "undefined" && self || require("node:worker_threads").parentPort;
let order;
let centers;
let chunks;
let mapping;
let cameraPosition;
let cameraDirection;
let forceUpdate = false;
const lastCameraPosition = { x: 0, y: 0, z: 0 };
const lastCameraDirection = { x: 0, y: 0, z: 0 };
const boundMin = { x: 0, y: 0, z: 0 };
const boundMax = { x: 0, y: 0, z: 0 };
let distances;
let countBuffer;
const numBins = 32;
const binCount = new Array(numBins).fill(0);
const binBase = new Array(numBins).fill(0);
const binDivider = new Array(numBins).fill(0);
const binarySearch = (m, n, compare_fn) => {
while (m <= n) {
const k = n + m >> 1;
const cmp = compare_fn(k);
if (cmp > 0) {
m = k + 1;
} else if (cmp < 0) {
n = k - 1;
} else {
return k;
}
}
return ~m;
};
const update = () => {
if (!order || !centers || centers.length === 0 || !cameraPosition || !cameraDirection) return;
const sortStartTime = performance.now();
const px = cameraPosition.x;
const py = cameraPosition.y;
const pz = cameraPosition.z;
const dx = cameraDirection.x;
const dy = cameraDirection.y;
const dz = cameraDirection.z;
const epsilon = 1e-3;
if (!forceUpdate && Math.abs(px - lastCameraPosition.x) < epsilon && Math.abs(py - lastCameraPosition.y) < epsilon && Math.abs(pz - lastCameraPosition.z) < epsilon && Math.abs(dx - lastCameraDirection.x) < epsilon && Math.abs(dy - lastCameraDirection.y) < epsilon && Math.abs(dz - lastCameraDirection.z) < epsilon) {
return;
}
forceUpdate = false;
lastCameraPosition.x = px;
lastCameraPosition.y = py;
lastCameraPosition.z = pz;
lastCameraDirection.x = dx;
lastCameraDirection.y = dy;
lastCameraDirection.z = dz;
let minDist;
let maxDist;
for (let i = 0; i < 8; ++i) {
const x = i & 1 ? boundMin.x : boundMax.x;
const y = i & 2 ? boundMin.y : boundMax.y;
const z = i & 4 ? boundMin.z : boundMax.z;
const d = x * dx + y * dy + z * dz;
if (i === 0) {
minDist = maxDist = d;
} else {
minDist = Math.min(minDist, d);
maxDist = Math.max(maxDist, d);
}
}
const numVertices = centers.length / 3;
const compareBits = Math.max(10, Math.min(20, Math.round(Math.log2(numVertices / 4))));
const bucketCount = 2 ** compareBits + 1;
if (distances?.length !== numVertices) {
distances = new Uint32Array(numVertices);
}
if (!countBuffer || countBuffer.length !== bucketCount) {
countBuffer = new Uint32Array(bucketCount);
} else {
countBuffer.fill(0);
}
const range = maxDist - minDist;
if (range < 1e-6) {
for (let i = 0; i < numVertices; ++i) {
distances[i] = 0;
countBuffer[0]++;
}
} else {
const numChunks = chunks.length / 4;
binCount.fill(0);
for (let i = 0; i < numChunks; ++i) {
const x = chunks[i * 4 + 0];
const y = chunks[i * 4 + 1];
const z = chunks[i * 4 + 2];
const r = chunks[i * 4 + 3];
const d = x * dx + y * dy + z * dz - minDist;
const binMin = Math.max(0, Math.floor((d - r) * numBins / range));
const binMax = Math.min(numBins, Math.ceil((d + r) * numBins / range));
for (let j = binMin; j < binMax; ++j) {
binCount[j]++;
}
}
const binTotal = binCount.reduce((a, b) => a + b, 0);
for (let i = 0; i < numBins; ++i) {
binDivider[i] = binCount[i] / binTotal * bucketCount >>> 0;
}
for (let i = 0; i < numBins; ++i) {
binBase[i] = i === 0 ? 0 : binBase[i - 1] + binDivider[i - 1];
}
const binRange = range / numBins;
let ii = 0;
for (let i = 0; i < numVertices; ++i) {
const x = centers[ii++];
const y = centers[ii++];
const z = centers[ii++];
const d = (x * dx + y * dy + z * dz - minDist) / binRange;
const bin = d >>> 0;
const sortKey = binBase[bin] + binDivider[bin] * (d - bin) >>> 0;
distances[i] = sortKey;
countBuffer[sortKey]++;
}
}
for (let i = 1; i < bucketCount; i++) {
countBuffer[i] += countBuffer[i - 1];
}
for (let i = 0; i < numVertices; i++) {
const distance = distances[i];
const destIndex = --countBuffer[distance];
order[destIndex] = i;
}
const cameraDist = px * dx + py * dy + pz * dz;
const dist = (i) => {
let o = order[i] * 3;
return centers[o++] * dx + centers[o++] * dy + centers[o] * dz - cameraDist;
};
const findZero = () => {
const result = binarySearch(0, numVertices - 1, (i) => -dist(i));
return Math.min(numVertices, Math.abs(result));
};
const count = dist(numVertices - 1) >= 0 ? findZero() : numVertices;
if (mapping) {
for (let i = 0; i < numVertices; ++i) {
order[i] = mapping[order[i]];
}
}
myself.postMessage({
order: order.buffer,
count,
sortTime: performance.now() - sortStartTime
}, [order.buffer]);
order = null;
};
myself.addEventListener("message", (message) => {
const msgData = message.data ?? message;
if (msgData.order) {
order = new Uint32Array(msgData.order);
}
if (msgData.centers) {
centers = new Float32Array(msgData.centers);
forceUpdate = true;
if (msgData.chunks) {
const chunksSrc = new Float32Array(msgData.chunks);
chunks = new Float32Array(msgData.chunks, 0, chunksSrc.length * 4 / 6);
boundMin.x = chunksSrc[0];
boundMin.y = chunksSrc[1];
boundMin.z = chunksSrc[2];
boundMax.x = chunksSrc[3];
boundMax.y = chunksSrc[4];
boundMax.z = chunksSrc[5];
for (let i = 0; i < chunksSrc.length / 6; ++i) {
const mx = chunksSrc[i * 6 + 0];
const my = chunksSrc[i * 6 + 1];
const mz = chunksSrc[i * 6 + 2];
const Mx = chunksSrc[i * 6 + 3];
const My = chunksSrc[i * 6 + 4];
const Mz = chunksSrc[i * 6 + 5];
chunks[i * 4 + 0] = (mx + Mx) * 0.5;
chunks[i * 4 + 1] = (my + My) * 0.5;
chunks[i * 4 + 2] = (mz + Mz) * 0.5;
chunks[i * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
if (mx < boundMin.x) boundMin.x = mx;
if (my < boundMin.y) boundMin.y = my;
if (mz < boundMin.z) boundMin.z = mz;
if (Mx > boundMax.x) boundMax.x = Mx;
if (My > boundMax.y) boundMax.y = My;
if (Mz > boundMax.z) boundMax.z = Mz;
}
} else {
const numVertices = centers.length / 3;
const numChunks = Math.ceil(numVertices / 256);
chunks = new Float32Array(numChunks * 4);
boundMin.x = boundMin.y = boundMin.z = Infinity;
boundMax.x = boundMax.y = boundMax.z = -Infinity;
let mx, my, mz, Mx, My, Mz;
for (let c = 0; c < numChunks; ++c) {
mx = my = mz = Infinity;
Mx = My = Mz = -Infinity;
const start = c * 256;
const end = Math.min(numVertices, (c + 1) * 256);
for (let i = start; i < end; ++i) {
const x = centers[i * 3 + 0];
const y = centers[i * 3 + 1];
const z = centers[i * 3 + 2];
const validX = Number.isFinite(x);
const validY = Number.isFinite(y);
const validZ = Number.isFinite(z);
if (!validX) centers[i * 3 + 0] = 0;
if (!validY) centers[i * 3 + 1] = 0;
if (!validZ) centers[i * 3 + 2] = 0;
if (!validX || !validY || !validZ) {
continue;
}
if (x < mx) mx = x;
else if (x > Mx) Mx = x;
if (y < my) my = y;
else if (y > My) My = y;
if (z < mz) mz = z;
else if (z > Mz) Mz = z;
if (x < boundMin.x) boundMin.x = x;
else if (x > boundMax.x) boundMax.x = x;
if (y < boundMin.y) boundMin.y = y;
else if (y > boundMax.y) boundMax.y = y;
if (z < boundMin.z) boundMin.z = z;
else if (z > boundMax.z) boundMax.z = z;
}
chunks[c * 4 + 0] = (mx + Mx) * 0.5;
chunks[c * 4 + 1] = (my + My) * 0.5;
chunks[c * 4 + 2] = (mz + Mz) * 0.5;
chunks[c * 4 + 3] = Math.sqrt((Mx - mx) ** 2 + (My - my) ** 2 + (Mz - mz) ** 2) * 0.5;
}
}
}
if (msgData.hasOwnProperty("mapping")) {
mapping = msgData.mapping ? new Uint32Array(msgData.mapping) : null;
forceUpdate = true;
}
if (msgData.cameraPosition) cameraPosition = msgData.cameraPosition;
if (msgData.cameraDirection) cameraDirection = msgData.cameraDirection;
update();
});
}
export {
SortWorker
};