UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

119 lines (118 loc) 4.12 kB
import { Frustum } from "../../core/shape/frustum.js"; import { Mat4 } from "../../core/math/mat4.js"; import { BUFFERUSAGE_COPY_DST } from "../../platform/graphics/constants.js"; import { StorageBuffer } from "../../platform/graphics/storage-buffer.js"; const _viewProjMat = new Mat4(); const _frustum = new Frustum(); const BOUNDS_ENTRY_FLOATS = 8; class GSplatFrustumCuller { device; boundsBuffer = null; totalBoundsEntries = 0; _allocatedBoundsEntries = 0; _boundsFloatView = null; _boundsUintView = null; _tmpSpheres = null; transformsBuffer = null; _allocatedTransformCount = 0; _transformsData = null; frustumPlanes = new Float32Array(24); fisheyeCameraPos = new Float32Array(3); fisheyeCameraForward = new Float32Array(3); fisheyeMaxTheta = Math.PI; constructor(device) { this.device = device; } destroy() { this.boundsBuffer?.destroy(); this.transformsBuffer?.destroy(); } updateBoundsData(boundsGroups) { let totalEntries = 0; for (let i = 0; i < boundsGroups.length; i++) { totalEntries += boundsGroups[i].numBoundsEntries; } this.totalBoundsEntries = totalEntries; if (totalEntries === 0) return; if (totalEntries > this._allocatedBoundsEntries) { this.boundsBuffer?.destroy(); this._allocatedBoundsEntries = totalEntries; this.boundsBuffer = new StorageBuffer(this.device, totalEntries * BOUNDS_ENTRY_FLOATS * 4, BUFFERUSAGE_COPY_DST); const ab = new ArrayBuffer(totalEntries * BOUNDS_ENTRY_FLOATS * 4); this._boundsFloatView = new Float32Array(ab); this._boundsUintView = new Uint32Array(ab); this._tmpSpheres = new Float32Array(totalEntries * 4); } const floatView = this._boundsFloatView; const uintView = this._boundsUintView; const tmpSpheres = this._tmpSpheres; for (let i = 0; i < boundsGroups.length; i++) { const group = boundsGroups[i]; const base = group.boundsBaseIndex; const count = group.numBoundsEntries; group.splat.writeBoundsSpheres(tmpSpheres, base * 4); for (let j = 0; j < count; j++) { const src = (base + j) * 4; const dst = (base + j) * BOUNDS_ENTRY_FLOATS; floatView[dst + 0] = tmpSpheres[src + 0]; floatView[dst + 1] = tmpSpheres[src + 1]; floatView[dst + 2] = tmpSpheres[src + 2]; floatView[dst + 3] = tmpSpheres[src + 3]; uintView[dst + 4] = i; } } this.boundsBuffer.write(0, floatView); } updateTransformsData(boundsGroups) { const numMatrices = boundsGroups.length; if (numMatrices === 0) return; if (numMatrices > this._allocatedTransformCount) { this.transformsBuffer?.destroy(); this._allocatedTransformCount = numMatrices; this.transformsBuffer = new StorageBuffer(this.device, numMatrices * 12 * 4, BUFFERUSAGE_COPY_DST); this._transformsData = new Float32Array(numMatrices * 12); } const data = this._transformsData; let offset = 0; for (let i = 0; i < boundsGroups.length; i++) { const m = boundsGroups[i].splat.node.getWorldTransform().data; data[offset++] = m[0]; data[offset++] = m[4]; data[offset++] = m[8]; data[offset++] = m[12]; data[offset++] = m[1]; data[offset++] = m[5]; data[offset++] = m[9]; data[offset++] = m[13]; data[offset++] = m[2]; data[offset++] = m[6]; data[offset++] = m[10]; data[offset++] = m[14]; } this.transformsBuffer.write(0, data); } computeFrustumPlanes(projectionMatrix, viewMatrix) { _viewProjMat.mul2(projectionMatrix, viewMatrix); _frustum.setFromMat4(_viewProjMat); const planes = this.frustumPlanes; for (let p = 0; p < 6; p++) { const plane = _frustum.planes[p]; planes[p * 4 + 0] = plane.normal.x; planes[p * 4 + 1] = plane.normal.y; planes[p * 4 + 2] = plane.normal.z; planes[p * 4 + 3] = plane.distance; } } setFisheyeData(cameraPos, cameraForward, maxTheta) { this.fisheyeCameraPos[0] = cameraPos.x; this.fisheyeCameraPos[1] = cameraPos.y; this.fisheyeCameraPos[2] = cameraPos.z; this.fisheyeCameraForward[0] = cameraForward.x; this.fisheyeCameraForward[1] = cameraForward.y; this.fisheyeCameraForward[2] = cameraForward.z; this.fisheyeMaxTheta = maxTheta; } } export { GSplatFrustumCuller };