import { Scene } from "../../../core/Scene"; import { Splat } from "../../../splats/Splat"; import DataWorker from "web-worker:./DataWorker.ts"; import loadWasm from "../../../wasm/data"; import { Matrix4 } from "../../../math/Matrix4"; class RenderData { public dataChanged = false; public transformsChanged = false; public colorTransformsChanged = false; private _splatIndices: Map; private _offsets: Map; private _data: Uint32Array; private _width: number; private _height: number; private _transforms: Float32Array; private _transformsWidth: number; private _transformsHeight: number; private _transformIndices: Uint32Array; private _transformIndicesWidth: number; private _transformIndicesHeight: number; private _colorTransforms: Float32Array; private _colorTransformsWidth: number; private _colorTransformsHeight: number; private _colorTransformIndices: Uint32Array; private _colorTransformIndicesWidth: number; private _colorTransformIndicesHeight: number; private _positions: Float32Array; private _rotations: Float32Array; private _scales: Float32Array; private _vertexCount: number; private _updating: Set = new Set(); private _dirty: Set = new Set(); private _worker: Worker; getSplat: (index: number) => Splat | null; getLocalIndex: (splat: Splat, index: number) => number; markDirty: (splat: Splat) => void; rebuild: () => void; dispose: () => void; constructor(scene: Scene) { let vertexCount = 0; let splatIndex = 0; this._splatIndices = new Map(); this._offsets = new Map(); const lookup = new Map(); for (const object of scene.objects) { if (object instanceof Splat) { this._splatIndices.set(object, splatIndex); this._offsets.set(object, vertexCount); lookup.set(vertexCount, object); vertexCount += object.data.vertexCount; splatIndex++; } } this._vertexCount = vertexCount; this._width = 2048; this._height = Math.ceil((2 * this.vertexCount) / this.width); this._data = new Uint32Array(this.width * this.height * 4); this._transformsWidth = 5; this._transformsHeight = lookup.size; this._transforms = new Float32Array(this._transformsWidth * this._transformsHeight * 4); this._transformIndicesWidth = 1024; this._transformIndicesHeight = Math.ceil(this.vertexCount / this._transformIndicesWidth); this._transformIndices = new Uint32Array(this._transformIndicesWidth * this._transformIndicesHeight); this._colorTransformsWidth = 4; this._colorTransformsHeight = 64; this._colorTransforms = new Float32Array(this._colorTransformsWidth * this._colorTransformsHeight * 4); this._colorTransforms.fill(0); this._colorTransforms[0] = 1; this._colorTransforms[5] = 1; this._colorTransforms[10] = 1; this._colorTransforms[15] = 1; this._colorTransformIndicesWidth = 1024; this._colorTransformIndicesHeight = Math.ceil(this.vertexCount / this._colorTransformIndicesWidth); this._colorTransformIndices = new Uint32Array( this._colorTransformIndicesWidth * this._colorTransformIndicesHeight, ); this.colorTransformIndices.fill(0); this._positions = new Float32Array(this.vertexCount * 3); this._rotations = new Float32Array(this.vertexCount * 4); this._scales = new Float32Array(this.vertexCount * 3); this._worker = new DataWorker(); const updateTransform = (splat: Splat) => { const splatIndex = this._splatIndices.get(splat) as number; this._transforms.set(splat.transform.buffer, splatIndex * 20); this._transforms[splatIndex * 20 + 16] = splat.selected ? 1 : 0; splat.positionChanged = false; splat.rotationChanged = false; splat.scaleChanged = false; splat.selectedChanged = false; this.transformsChanged = true; }; const updateColorTransforms = () => { let colorTransformsChanged = false; for (const splat of this._splatIndices.keys()) { if (splat.colorTransformChanged) { colorTransformsChanged = true; break; } } if (!colorTransformsChanged) { return; } const colorTransformsMap: Matrix4[] = [new Matrix4()]; this._colorTransformIndices.fill(0); let i = 1; for (const splat of this._splatIndices.keys()) { const offset = this._offsets.get(splat) as number; for (const colorTransform of splat.colorTransforms) { if (!colorTransformsMap.includes(colorTransform)) { colorTransformsMap.push(colorTransform); i++; } } for (const index of splat.colorTransformsMap.keys()) { const colorTransformIndex = splat.colorTransformsMap.get(index) as number; this._colorTransformIndices[index + offset] = colorTransformIndex + i - 1; } splat.colorTransformChanged = false; } for (let index = 0; index < colorTransformsMap.length; index++) { const colorTransform = colorTransformsMap[index]; this._colorTransforms.set(colorTransform.buffer, index * 16); } this.colorTransformsChanged = true; }; this._worker.onmessage = (e) => { if (e.data.response) { const response = e.data.response; const splat = lookup.get(response.offset) as Splat; updateTransform(splat); updateColorTransforms(); const splatIndex = this._splatIndices.get(splat) as number; for (let i = 0; i < splat.data.vertexCount; i++) { this._transformIndices[response.offset + i] = splatIndex; } this._data.set(response.data, response.offset * 8); splat.data.reattach( response.positions, response.rotations, response.scales, response.colors, response.selection, ); this._positions.set(response.worldPositions, response.offset * 3); this._rotations.set(response.worldRotations, response.offset * 4); this._scales.set(response.worldScales, response.offset * 3); this._updating.delete(splat); splat.selectedChanged = false; this.dataChanged = true; } }; // eslint-disable-next-line @typescript-eslint/no-explicit-any let wasmModule: any; async function initWasm() { wasmModule = await loadWasm(); } initWasm(); async function waitForWasm() { while (!wasmModule) { await new Promise((resolve) => setTimeout(resolve, 0)); } } const buildImmediate = (splat: Splat) => { if (!wasmModule) { waitForWasm().then(() => { buildImmediate(splat); }); return; } updateTransform(splat); const positionsPtr = wasmModule._malloc(3 * splat.data.vertexCount * 4); const rotationsPtr = wasmModule._malloc(4 * splat.data.vertexCount * 4); const scalesPtr = wasmModule._malloc(3 * splat.data.vertexCount * 4); const colorsPtr = wasmModule._malloc(4 * splat.data.vertexCount); const selectionPtr = wasmModule._malloc(splat.data.vertexCount); const dataPtr = wasmModule._malloc(8 * splat.data.vertexCount * 4); const worldPositionsPtr = wasmModule._malloc(3 * splat.data.vertexCount * 4); const worldRotationsPtr = wasmModule._malloc(4 * splat.data.vertexCount * 4); const worldScalesPtr = wasmModule._malloc(3 * splat.data.vertexCount * 4); wasmModule.HEAPF32.set(splat.data.positions, positionsPtr / 4); wasmModule.HEAPF32.set(splat.data.rotations, rotationsPtr / 4); wasmModule.HEAPF32.set(splat.data.scales, scalesPtr / 4); wasmModule.HEAPU8.set(splat.data.colors, colorsPtr); wasmModule.HEAPU8.set(splat.data.selection, selectionPtr); wasmModule._pack( splat.selected, splat.data.vertexCount, positionsPtr, rotationsPtr, scalesPtr, colorsPtr, selectionPtr, dataPtr, worldPositionsPtr, worldRotationsPtr, worldScalesPtr, ); const outData = new Uint32Array(wasmModule.HEAPU32.buffer, dataPtr, splat.data.vertexCount * 8); const worldPositions = new Float32Array( wasmModule.HEAPF32.buffer, worldPositionsPtr, splat.data.vertexCount * 3, ); const worldRotations = new Float32Array( wasmModule.HEAPF32.buffer, worldRotationsPtr, splat.data.vertexCount * 4, ); const worldScales = new Float32Array(wasmModule.HEAPF32.buffer, worldScalesPtr, splat.data.vertexCount * 3); const splatIndex = this._splatIndices.get(splat) as number; const offset = this._offsets.get(splat) as number; for (let i = 0; i < splat.data.vertexCount; i++) { this._transformIndices[offset + i] = splatIndex; } this._data.set(outData, offset * 8); this._positions.set(worldPositions, offset * 3); this._rotations.set(worldRotations, offset * 4); this._scales.set(worldScales, offset * 3); wasmModule._free(positionsPtr); wasmModule._free(rotationsPtr); wasmModule._free(scalesPtr); wasmModule._free(colorsPtr); wasmModule._free(selectionPtr); wasmModule._free(dataPtr); wasmModule._free(worldPositionsPtr); wasmModule._free(worldRotationsPtr); wasmModule._free(worldScalesPtr); this.dataChanged = true; this.colorTransformsChanged = true; }; const build = (splat: Splat) => { if (splat.positionChanged || splat.rotationChanged || splat.scaleChanged || splat.selectedChanged) { updateTransform(splat); } if (splat.colorTransformChanged) { updateColorTransforms(); } if (!splat.data.changed || splat.data.detached) return; const serializedSplat = { position: new Float32Array(splat.position.flat()), rotation: new Float32Array(splat.rotation.flat()), scale: new Float32Array(splat.scale.flat()), selected: splat.selected, vertexCount: splat.data.vertexCount, positions: splat.data.positions, rotations: splat.data.rotations, scales: splat.data.scales, colors: splat.data.colors, selection: splat.data.selection, offset: this._offsets.get(splat) as number, }; this._worker.postMessage( { splat: serializedSplat, }, [ serializedSplat.position.buffer, serializedSplat.rotation.buffer, serializedSplat.scale.buffer, serializedSplat.positions.buffer, serializedSplat.rotations.buffer, serializedSplat.scales.buffer, serializedSplat.colors.buffer, serializedSplat.selection.buffer, ], ); this._updating.add(splat); splat.data.detached = true; }; this.getSplat = (index: number) => { let splat = null; for (const [key, value] of this._offsets) { if (index >= value) { splat = key; } else { break; } } return splat; }; this.getLocalIndex = (splat: Splat, index: number) => { const offset = this._offsets.get(splat) as number; return index - offset; }; this.markDirty = (splat: Splat) => { this._dirty.add(splat); }; this.rebuild = () => { for (const splat of this._dirty) { build(splat); } this._dirty.clear(); }; this.dispose = () => { this._worker.terminate(); }; for (const splat of this._splatIndices.keys()) { buildImmediate(splat); } updateColorTransforms(); } get offsets() { return this._offsets; } get data() { return this._data; } get width() { return this._width; } get height() { return this._height; } get transforms() { return this._transforms; } get transformsWidth() { return this._transformsWidth; } get transformsHeight() { return this._transformsHeight; } get transformIndices() { return this._transformIndices; } get transformIndicesWidth() { return this._transformIndicesWidth; } get transformIndicesHeight() { return this._transformIndicesHeight; } get colorTransforms() { return this._colorTransforms; } get colorTransformsWidth() { return this._colorTransformsWidth; } get colorTransformsHeight() { return this._colorTransformsHeight; } get colorTransformIndices() { return this._colorTransformIndices; } get colorTransformIndicesWidth() { return this._colorTransformIndicesWidth; } get colorTransformIndicesHeight() { return this._colorTransformIndicesHeight; } get positions() { return this._positions; } get rotations() { return this._rotations; } get scales() { return this._scales; } get vertexCount() { return this._vertexCount; } get needsRebuild() { return this._dirty.size > 0; } get updating() { return this._updating.size > 0; } } export { RenderData };