UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

242 lines (241 loc) 8.13 kB
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 };