Spaces:
Running
Running
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<Splat, number>; | |
private _offsets: Map<Splat, number>; | |
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<Splat> = new Set<Splat>(); | |
private _dirty: Set<Splat> = new Set<Splat>(); | |
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<Splat, number>(); | |
this._offsets = new Map<Splat, number>(); | |
const lookup = new Map<number, Splat>(); | |
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 }; | |