playcanvas
Version:
PlayCanvas WebGL game engine
279 lines (277 loc) • 10.2 kB
JavaScript
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 };