playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
189 lines (188 loc) • 6.59 kB
JavaScript
import { GSplatManager } from "./gsplat-manager.js";
import { SetUtils } from "../../core/set-utils.js";
import { GSPLAT_FORWARD, GSPLAT_SHADOW } from "../constants.js";
import { GSplatResourceCleanup } from "../gsplat/gsplat-resource-cleanup.js";
const tempLayersToRemove = [];
class GSplatLayerData {
gsplatManager = null;
gsplatManagerShadow = null;
constructor(device, director, layer, camera) {
this.updateConfiguration(device, director, layer, camera);
}
createManager(device, director, layer, cameraNode, camera, renderMode) {
const manager = new GSplatManager(device, director, layer, cameraNode);
manager.setRenderMode(renderMode);
if (director.eventHandler) {
director.eventHandler.fire("material:created", manager.material, camera, layer);
}
return manager;
}
updateConfiguration(device, director, layer, camera) {
const cameraNode = camera.node;
const hasNormalPlacements = layer.gsplatPlacements.length > 0;
const hasShadowCasters = layer.gsplatShadowCasters.length > 0;
const setsEqual = SetUtils.equals(layer.gsplatPlacementsSet, layer.gsplatShadowCastersSet);
const useSharedManager = setsEqual && hasNormalPlacements;
const desiredMainMode = useSharedManager ? GSPLAT_FORWARD | GSPLAT_SHADOW : hasNormalPlacements ? GSPLAT_FORWARD : 0;
const desiredShadowMode = useSharedManager ? 0 : hasShadowCasters ? GSPLAT_SHADOW : 0;
if (desiredMainMode) {
if (this.gsplatManager) {
this.gsplatManager.setRenderMode(desiredMainMode);
} else {
this.gsplatManager = this.createManager(device, director, layer, cameraNode, camera, desiredMainMode);
}
} else if (this.gsplatManager) {
this.gsplatManager.destroy();
this.gsplatManager = null;
}
if (desiredShadowMode) {
if (this.gsplatManagerShadow) {
this.gsplatManagerShadow.setRenderMode(desiredShadowMode);
} else {
this.gsplatManagerShadow = this.createManager(device, director, layer, cameraNode, camera, desiredShadowMode);
}
} else if (this.gsplatManagerShadow) {
this.gsplatManagerShadow.destroy();
this.gsplatManagerShadow = null;
}
}
destroy() {
this.gsplatManager?.destroy();
this.gsplatManager = null;
this.gsplatManagerShadow?.destroy();
this.gsplatManagerShadow = null;
}
}
class GSplatCameraData {
layersMap = /* @__PURE__ */ new Map();
destroy() {
this.layersMap.forEach((layerData) => layerData.destroy());
this.layersMap.clear();
}
removeLayerData(layer) {
const layerData = this.layersMap.get(layer);
if (layerData) {
layerData.destroy();
this.layersMap.delete(layer);
}
}
getLayerData(device, director, layer, camera) {
let layerData = this.layersMap.get(layer);
if (!layerData) {
layerData = new GSplatLayerData(device, director, layer, camera);
this.layersMap.set(layer, layerData);
}
return layerData;
}
}
class GSplatDirector {
device;
camerasMap = /* @__PURE__ */ new Map();
scene;
eventHandler;
constructor(device, renderer, scene, eventHandler) {
this.device = device;
this.renderer = renderer;
this.scene = scene;
this.eventHandler = eventHandler;
}
destroy() {
this.camerasMap.forEach((cameraData) => cameraData.destroy());
this.camerasMap.clear();
}
getCameraData(camera) {
let cameraData = this.camerasMap.get(camera);
if (!cameraData) {
cameraData = new GSplatCameraData();
this.camerasMap.set(camera, cameraData);
}
return cameraData;
}
prepareForPicking(camera, width, height, layer) {
const cameraData = this.camerasMap.get(camera);
if (!cameraData) return null;
const layerData = cameraData.layersMap.get(layer);
if (!layerData?.gsplatManager) return null;
return layerData.gsplatManager.prepareForPicking(camera, width, height);
}
update(comp) {
GSplatResourceCleanup.process(this.device);
this.camerasMap.forEach((cameraData, camera) => {
if (!comp.camerasSet.has(camera)) {
cameraData.destroy();
this.camerasMap.delete(camera);
} else {
cameraData.layersMap.forEach((layerData, layer) => {
if (!camera.layersSet.has(layer.id) || !layer.enabled) {
tempLayersToRemove.push(layer);
}
});
for (let i = 0; i < tempLayersToRemove.length; i++) {
const layer = tempLayersToRemove[i];
const layerData = cameraData.layersMap.get(layer);
if (layerData) {
layerData.destroy();
cameraData.layersMap.delete(layer);
}
}
tempLayersToRemove.length = 0;
}
});
let gsplatCount = 0;
let bufferCopyUploaded = 0;
let bufferCopyTotal = 0;
const camerasComponents = comp.cameras;
for (let i = 0; i < camerasComponents.length; i++) {
const camera = camerasComponents[i].camera;
let cameraData = this.camerasMap.get(camera);
const layerIds = camera.layers;
for (let j = 0; j < layerIds.length; j++) {
const layer = comp.getLayerById(layerIds[j]);
if (layer?.enabled) {
if (layer.gsplatPlacementsDirty || !cameraData) {
const hasNormalPlacements = layer.gsplatPlacements.length > 0;
const hasShadowCasters = layer.gsplatShadowCasters.length > 0;
if (!hasNormalPlacements && !hasShadowCasters) {
if (cameraData) {
cameraData.removeLayerData(layer);
}
} else {
cameraData ?? (cameraData = this.getCameraData(camera));
const layerData = cameraData.getLayerData(this.device, this, layer, camera);
layerData.updateConfiguration(this.device, this, layer, camera);
if (layerData.gsplatManager) {
layerData.gsplatManager.reconcile(layer.gsplatPlacements);
}
if (layerData.gsplatManagerShadow) {
layerData.gsplatManagerShadow.reconcile(layer.gsplatShadowCasters);
}
}
}
}
}
if (cameraData) {
for (const layerData of cameraData.layersMap.values()) {
if (layerData.gsplatManager) {
gsplatCount += layerData.gsplatManager.update();
bufferCopyUploaded += layerData.gsplatManager.bufferCopyUploaded;
bufferCopyTotal += layerData.gsplatManager.bufferCopyTotal;
}
if (layerData.gsplatManagerShadow) {
gsplatCount += layerData.gsplatManagerShadow.update();
bufferCopyUploaded += layerData.gsplatManagerShadow.bufferCopyUploaded;
bufferCopyTotal += layerData.gsplatManagerShadow.bufferCopyTotal;
}
}
}
}
this.renderer._gsplatCount = gsplatCount;
this.renderer._gsplatBufferCopy = bufferCopyTotal > 0 ? bufferCopyUploaded / bufferCopyTotal * 100 : 0;
this.scene.gsplat.frameEnd();
for (let i = 0; i < comp.layerList.length; i++) {
comp.layerList[i].gsplatPlacementsDirty = false;
}
}
}
export {
GSplatDirector
};