UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

279 lines (277 loc) 10.2 kB
function UnifiedSortWorker() { const myself = typeof self !== 'undefined' && self || require('node:worker_threads').parentPort; const centersMap = new Map(); let centersData; let distances; let countBuffer; let _radialSort = false; const numBins = 32; const binBase = new Array(numBins).fill(0); const binDivider = new Array(numBins).fill(0); const weightTiers = [ { maxDistance: 0, weight: 40.0 }, { maxDistance: 2, weight: 20.0 }, { maxDistance: 5, weight: 8.0 }, { maxDistance: 10, weight: 3.0 }, { maxDistance: Infinity, weight: 1.0 } ]; const weightByDistance = new Array(numBins); for(let dist = 0; dist < numBins; ++dist){ let weight = 1.0; for(let j = 0; j < weightTiers.length; ++j){ if (dist <= weightTiers[j].maxDistance) { weight = weightTiers[j].weight; break; } } weightByDistance[dist] = weight; } const setupCameraRelativeBins = (cameraBin, bucketCount)=>{ const totalBudget = bucketCount; const bitsPerBin = []; for(let i = 0; i < numBins; ++i){ const distFromCamera = Math.abs(i - cameraBin); bitsPerBin[i] = weightByDistance[distFromCamera]; } const totalWeight = bitsPerBin.reduce((a, b)=>a + b, 0); let accumulated = 0; for(let i = 0; i < numBins; ++i){ binDivider[i] = Math.max(1, Math.floor(bitsPerBin[i] / totalWeight * totalBudget)); binBase[i] = accumulated; accumulated += binDivider[i]; } if (accumulated > bucketCount) { const excess = accumulated - bucketCount; binDivider[numBins - 1] = Math.max(1, binDivider[numBins - 1] - excess); } binBase[numBins] = binBase[numBins - 1] + binDivider[numBins - 1]; binDivider[numBins] = 0; }; const evaluateSortKeysCommon = (sortParams, minDist, range, distances, countBuffer, centersData, processSplatFn)=>{ const { ids, lineStarts, padding, intervals, textureSize } = centersData; const invBinRange = numBins / range; for(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){ const params = sortParams[paramIdx]; const id = ids[paramIdx]; const centers = centersMap.get(id); if (!centers) { console.error('UnifiedSortWorker: No centers found for id', id); } let targetIndex = lineStarts[paramIdx] * textureSize; const intervalsArray = intervals[paramIdx].length > 0 ? intervals[paramIdx] : [ 0, centers.length / 3 ]; for(let i = 0; i < intervalsArray.length; i += 2){ const intervalStart = intervalsArray[i] * 3; const intervalEnd = intervalsArray[i + 1] * 3; targetIndex = processSplatFn(centers, params, intervalStart, intervalEnd, targetIndex, invBinRange, minDist, range, distances, countBuffer); } const pad = padding[paramIdx]; countBuffer[0] += pad; distances.fill(0, targetIndex, targetIndex + pad); targetIndex += pad; } }; const evaluateSortKeysLinear = (sortParams, minDist, range, distances, countBuffer, centersData)=>{ evaluateSortKeysCommon(sortParams, minDist, range, distances, countBuffer, centersData, (centers, params, intervalStart, intervalEnd, targetIndex, invBinRange, minDist, range, distances, countBuffer)=>{ const { transformedDirection, offset, scale } = params; const dx = transformedDirection.x; const dy = transformedDirection.y; const dz = transformedDirection.z; const sdx = dx * scale; const sdy = dy * scale; const sdz = dz * scale; const add = offset - minDist; for(let srcIndex = intervalStart; srcIndex < intervalEnd; srcIndex += 3){ const x = centers[srcIndex]; const y = centers[srcIndex + 1]; const z = centers[srcIndex + 2]; const dist = x * sdx + y * sdy + z * sdz + add; const d = dist * invBinRange; const bin = d >>> 0; const sortKey = binBase[bin] + binDivider[bin] * (d - bin) >>> 0; distances[targetIndex++] = sortKey; countBuffer[sortKey]++; } return targetIndex; }); }; const evaluateSortKeysRadial = (sortParams, minDist, range, distances, countBuffer, centersData)=>{ evaluateSortKeysCommon(sortParams, minDist, range, distances, countBuffer, centersData, (centers, params, intervalStart, intervalEnd, targetIndex, invBinRange, minDist, range, distances, countBuffer)=>{ const { transformedPosition, scale } = params; const cx = transformedPosition.x; const cy = transformedPosition.y; const cz = transformedPosition.z; for(let srcIndex = intervalStart; srcIndex < intervalEnd; srcIndex += 3){ const dx = centers[srcIndex] - cx; const dy = centers[srcIndex + 1] - cy; const dz = centers[srcIndex + 2] - cz; const distSq = dx * dx + dy * dy + dz * dz; const dist = Math.sqrt(distSq) * scale; const invertedDist = range - dist; const d = invertedDist * invBinRange; const bin = d >>> 0; const sortKey = binBase[bin] + binDivider[bin] * (d - bin) >>> 0; distances[targetIndex++] = sortKey; countBuffer[sortKey]++; } return targetIndex; }); }; const countingSort = (bucketCount, countBuffer, numVertices, distances, order)=>{ 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 computeEffectiveDistanceRangeLinear = (sortParams)=>{ let minDist = Infinity; let maxDist = -Infinity; for(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){ const params = sortParams[paramIdx]; const { transformedDirection, offset, scale, aabbMin, aabbMax } = params; const dx = transformedDirection.x; const dy = transformedDirection.y; const dz = transformedDirection.z; const pxMin = dx >= 0 ? aabbMin[0] : aabbMax[0]; const pyMin = dy >= 0 ? aabbMin[1] : aabbMax[1]; const pzMin = dz >= 0 ? aabbMin[2] : aabbMax[2]; const pxMax = dx >= 0 ? aabbMax[0] : aabbMin[0]; const pyMax = dy >= 0 ? aabbMax[1] : aabbMin[1]; const pzMax = dz >= 0 ? aabbMax[2] : aabbMin[2]; const dMin = pxMin * dx + pyMin * dy + pzMin * dz; const dMax = pxMax * dx + pyMax * dy + pzMax * dz; const eMin = dMin * scale + offset; const eMax = dMax * scale + offset; const localMin = Math.min(eMin, eMax); const localMax = Math.max(eMin, eMax); if (localMin < minDist) minDist = localMin; if (localMax > maxDist) maxDist = localMax; } if (minDist === Infinity) { minDist = 0; maxDist = 0; } return { minDist, maxDist }; }; const computeEffectiveDistanceRangeRadial = (sortParams)=>{ let maxDist = -Infinity; for(let paramIdx = 0; paramIdx < sortParams.length; paramIdx++){ const params = sortParams[paramIdx]; const { transformedPosition, scale, aabbMin, aabbMax } = params; const cx = transformedPosition.x; const cy = transformedPosition.y; const cz = transformedPosition.z; for(let i = 0; i < 8; i++){ const px = i & 1 ? aabbMax[0] : aabbMin[0]; const py = i & 2 ? aabbMax[1] : aabbMin[1]; const pz = i & 4 ? aabbMax[2] : aabbMin[2]; const dx = px - cx; const dy = py - cy; const dz = pz - cz; const distSq = dx * dx + dy * dy + dz * dz; const dist = Math.sqrt(distSq) * scale; if (dist > maxDist) maxDist = dist; } } const minDist = 0; if (maxDist < 0) { maxDist = 0; } return { minDist, maxDist }; }; const sort = (sortParams, order, centersData)=>{ const { minDist, maxDist } = _radialSort ? computeEffectiveDistanceRangeRadial(sortParams) : computeEffectiveDistanceRangeLinear(sortParams); const numVertices = centersData.totalUsedPixels; 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; let cameraBin; if (_radialSort) { cameraBin = numBins - 1; } else { const cameraOffsetFromRangeStart = 0 - minDist; const cameraBinFloat = cameraOffsetFromRangeStart / range * numBins; cameraBin = Math.max(0, Math.min(numBins - 1, Math.floor(cameraBinFloat))); } setupCameraRelativeBins(cameraBin, bucketCount); if (_radialSort) { evaluateSortKeysRadial(sortParams, minDist, range, distances, countBuffer, centersData); } else { evaluateSortKeysLinear(sortParams, minDist, range, distances, countBuffer, centersData); } countingSort(bucketCount, countBuffer, numVertices, distances, order); const count = numVertices; const transferList = [ order.buffer ]; const response = { order: order.buffer, count, version: centersData.version }; myself.postMessage(response, transferList); }; myself.addEventListener('message', (message)=>{ const msgData = message.data ?? message; switch(msgData.command){ case 'addCenters': { centersMap.set(msgData.id, new Float32Array(msgData.centers)); break; } case 'removeCenters': { centersMap.delete(msgData.id); break; } case 'sort': { _radialSort = msgData.radialSorting || false; const order = new Uint32Array(msgData.order); sort(msgData.sortParams, order, centersData); break; } case 'intervals': { centersData = msgData; break; } } }); } export { UnifiedSortWorker };