@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
236 lines (233 loc) • 6.68 kB
JavaScript
import { EventHandler } from '../../core/event-handler.js';
import { TEXTURELOCK_READ } from '../../platform/graphics/constants.js';
function SortWorker() {
let order;
let centers;
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 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 = () => {
var _distances;
if (!order || !centers || centers.length === 0 || !cameraPosition || !cameraDirection) return;
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 = 0.001;
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) - px;
const y = (i & 2 ? boundMin.y : boundMax.y) - py;
const z = (i & 4 ? boundMin.z : boundMax.z) - pz;
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 = distances) == null ? void 0 : _distances.length) !== numVertices) {
distances = new Uint32Array(numVertices);
}
if (!countBuffer || countBuffer.length !== bucketCount) {
countBuffer = new Uint32Array(bucketCount);
} else {
countBuffer.fill(0);
}
const range = maxDist - minDist;
const divider = range < 1e-6 ? 0 : 1 / range * 2 ** compareBits;
for (let i = 0; i < numVertices; ++i) {
const istride = i * 3;
const x = centers[istride + 0] - px;
const y = centers[istride + 1] - py;
const z = centers[istride + 2] - pz;
const d = x * dx + y * dy + z * dz;
const sortKey = Math.floor((d - minDist) * divider);
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 dist = i => distances[order[i]] / divider + minDist;
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]];
}
}
self.postMessage({
order: order.buffer,
count
}, [order.buffer]);
order = null;
};
self.onmessage = message => {
if (message.data.order) {
order = new Uint32Array(message.data.order);
}
if (message.data.centers) {
centers = new Float32Array(message.data.centers);
boundMin.x = boundMax.x = centers[0];
boundMin.y = boundMax.y = centers[1];
boundMin.z = boundMax.z = centers[2];
const numVertices = centers.length / 3;
for (let i = 1; i < numVertices; ++i) {
const x = centers[i * 3 + 0];
const y = centers[i * 3 + 1];
const z = centers[i * 3 + 2];
boundMin.x = Math.min(boundMin.x, x);
boundMin.y = Math.min(boundMin.y, y);
boundMin.z = Math.min(boundMin.z, z);
boundMax.x = Math.max(boundMax.x, x);
boundMax.y = Math.max(boundMax.y, y);
boundMax.z = Math.max(boundMax.z, z);
}
forceUpdate = true;
}
if (message.data.hasOwnProperty('mapping')) {
mapping = message.data.mapping ? new Uint32Array(message.data.mapping) : null;
forceUpdate = true;
}
if (message.data.cameraPosition) cameraPosition = message.data.cameraPosition;
if (message.data.cameraDirection) cameraDirection = message.data.cameraDirection;
update();
};
}
class GSplatSorter extends EventHandler {
constructor() {
super();
this.worker = void 0;
this.orderTexture = void 0;
this.centers = void 0;
this.worker = new Worker(URL.createObjectURL(new Blob([`(${SortWorker.toString()})()`], {
type: 'application/javascript'
})));
this.worker.onmessage = message => {
const newOrder = message.data.order;
const oldOrder = this.orderTexture._levels[0].buffer;
this.worker.postMessage({
order: oldOrder
}, [oldOrder]);
this.orderTexture._levels[0] = new Uint32Array(newOrder);
this.orderTexture.upload();
this.fire('updated', message.data.count);
};
}
destroy() {
this.worker.terminate();
this.worker = null;
}
init(orderTexture, centers) {
this.orderTexture = orderTexture;
this.centers = centers.slice();
const orderBuffer = this.orderTexture.lock({
mode: TEXTURELOCK_READ
}).buffer.slice();
this.orderTexture.unlock();
this.worker.postMessage({
order: orderBuffer,
centers: centers.buffer
}, [orderBuffer, centers.buffer]);
}
setMapping(mapping) {
if (mapping) {
const centers = new Float32Array(mapping.length * 3);
for (let i = 0; i < mapping.length; ++i) {
const src = mapping[i] * 3;
const dst = i * 3;
centers[dst + 0] = this.centers[src + 0];
centers[dst + 1] = this.centers[src + 1];
centers[dst + 2] = this.centers[src + 2];
}
this.worker.postMessage({
centers: centers.buffer,
mapping: mapping.buffer
}, [centers.buffer, mapping.buffer]);
} else {
const centers = this.centers.slice();
this.worker.postMessage({
centers: centers.buffer,
mapping: null
}, [centers.buffer]);
}
}
setCamera(pos, dir) {
this.worker.postMessage({
cameraPosition: {
x: pos.x,
y: pos.y,
z: pos.z
},
cameraDirection: {
x: dir.x,
y: dir.y,
z: dir.z
}
});
}
}
export { GSplatSorter };