playcanvas
Version:
PlayCanvas WebGL game engine
179 lines (176 loc) • 6.97 kB
JavaScript
import { GSplatManager } from './gsplat-manager.js';
/**
* @import { LayerComposition } from '../composition/layer-composition.js'
* @import { Camera } from '../camera.js'
* @import { Layer } from '../layer.js'
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { GraphNode } from '../graph-node.js'
* @import { Scene } from '../scene.js'
* @import { Renderer } from '../renderer/renderer.js'
* @import { EventHandler } from '../../core/event-handler.js'
*/ const tempLayersToRemove = [];
/**
* Per layer data the director keeps track of.
*
* @ignore
*/ class GSplatLayerData {
/**
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatDirector} director - The director.
* @param {Layer} layer - The layer.
* @param {GraphNode} cameraNode - The camera node.
*/ constructor(device, director, layer, cameraNode){
this.gsplatManager = new GSplatManager(device, director, layer, cameraNode);
}
destroy() {
this.gsplatManager.destroy();
}
}
/**
* Per camera data the director keeps track of.
*
* @ignore
*/ class GSplatCameraData {
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, cameraNode) {
let layerData = this.layersMap.get(layer);
if (!layerData) {
layerData = new GSplatLayerData(device, director, layer, cameraNode);
this.layersMap.set(layer, layerData);
// Fire event that material was created
const material = layerData.gsplatManager.material;
if (material && director.eventHandler) {
director.eventHandler.fire('material:created', material, cameraNode.camera, layer);
}
}
return layerData;
}
constructor(){
/**
* @type {Map<Layer, GSplatLayerData>}
*/ this.layersMap = new Map();
}
}
/**
* Class responsible for managing {@link GSplatManager} instances for Cameras and their Layers.
*
* @ignore
*/ class GSplatDirector {
/**
* @param {GraphicsDevice} device - The graphics device.
* @param {Renderer} renderer - The renderer.
* @param {Scene} scene - The scene.
* @param {EventHandler} eventHandler - Event handler for firing events.
*/ constructor(device, renderer, scene, eventHandler){
/**
* Per camera data.
*
* @type {Map<Camera, GSplatCameraData>}
*/ this.camerasMap = new Map();
this.device = device;
this.renderer = renderer;
this.scene = scene;
this.eventHandler = eventHandler;
}
destroy() {
// destroy all gsplat managers
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;
}
/**
* Updates the director for the given layer composition cameras and layers.
*
* @param {LayerComposition} comp - The layer composition.
*/ update(comp) {
// remove camera / layer entires for cameras / layers no longer in the composition
this.camerasMap.forEach((cameraData, camera)=>{
// camera is no longer in the composition
if (!comp.camerasSet.has(camera)) {
cameraData.destroy();
this.camerasMap.delete(camera);
} else {
// remove all layerdata for removed / disabled layers of this camera
// Collect layers to remove (don't modify map during iteration)
cameraData.layersMap.forEach((layerData, layer)=>{
if (!camera.layersSet.has(layer.id) || !layer.enabled) {
tempLayersToRemove.push(layer);
}
});
// Now safely remove them
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);
}
}
// Clear to avoid dangling references
tempLayersToRemove.length = 0;
}
});
let gsplatCount = 0;
// for all cameras in the composition
const camerasComponents = comp.cameras;
for(let i = 0; i < camerasComponents.length; i++){
const camera = camerasComponents[i].camera;
let cameraData = this.camerasMap.get(camera);
// for all of its layers
const layerIds = camera.layers;
for(let j = 0; j < layerIds.length; j++){
const layer = comp.getLayerById(layerIds[j]);
if (layer?.enabled) {
// if layer's splat placements were modified, or new camera
if (layer.gsplatPlacementsDirty || !cameraData) {
// no splats on layer
const placements = layer.gsplatPlacements;
if (placements.length === 0) {
// remove gsplat manager if it exists
if (cameraData) {
cameraData.removeLayerData(layer);
}
} else {
// update gsplat manager with modified placements
cameraData ??= this.getCameraData(camera);
const layerData = cameraData.getLayerData(this.device, this, layer, camera.node);
layerData.gsplatManager.reconcile(placements);
}
}
}
}
// update gsplat managers
if (cameraData) {
for (const layerData of cameraData.layersMap.values()){
gsplatCount += layerData.gsplatManager.update();
}
}
}
// update stats
this.renderer._gsplatCount = gsplatCount;
// clear global gsplat params dirty flag after all updates for this camera
this.scene.gsplat.dirty = false;
// clear dirty flags on all layers of the composition
for(let i = 0; i < comp.layerList.length; i++){
comp.layerList[i].gsplatPlacementsDirty = false;
}
}
}
export { GSplatDirector };