playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
122 lines (121 loc) • 4.19 kB
JavaScript
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);
}
setFrustumPlanes(frustum) {
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;
}
}
computeFrustumPlanes(projectionMatrix, viewMatrix) {
_viewProjMat.mul2(projectionMatrix, viewMatrix);
_frustum.setFromMat4(_viewProjMat);
this.setFrustumPlanes(_frustum);
}
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
};