playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
452 lines (451 loc) • 12.3 kB
JavaScript
import { hashCode } from "../../../core/hash.js";
import { LAYERID_WORLD, WORKBUFFER_UPDATE_AUTO } from "../../../scene/constants.js";
import { GSplatInstance } from "../../../scene/gsplat/gsplat-instance.js";
import { Asset } from "../../asset/asset.js";
import { AssetReference } from "../../asset/asset-reference.js";
import { Component } from "../component.js";
import { GSplatPlacement } from "../../../scene/gsplat-unified/gsplat-placement.js";
import { PickerId } from "../../../scene/picker-id.js";
const UNIFIED_LEGACY_HINT = "GSplatComponent#unified now defaults to true (unified rendering). To temporarily restore the deprecated legacy behavior, explicitly set unified=false when creating the component \u2014 note that non-unified mode will be removed in a future release.";
class GSplatComponent extends Component {
_layers = [LAYERID_WORLD];
// assign to the default world layer
_instance = null;
_placement = null;
_id = PickerId.get();
_materialTmp = null;
_highQualitySH = true;
_lodBaseDistance = 5;
_lodMultiplier = 3;
_customAabb = null;
_assetReference;
_resource = null;
_evtLayersChanged = null;
_evtLayerAdded = null;
_evtLayerRemoved = null;
_castShadows = false;
_unified = true;
_parameters = /* @__PURE__ */ new Map();
_workBufferUpdate = WORKBUFFER_UPDATE_AUTO;
_workBufferModifier = null;
constructor(system, entity) {
super(system, entity);
this._assetReference = new AssetReference(
"asset",
this,
system.app.assets,
{
add: this._onGSplatAssetAdded,
load: this._onGSplatAssetLoad,
remove: this._onGSplatAssetRemove,
unload: this._onGSplatAssetUnload
},
this
);
entity.on("remove", this.onRemoveChild, this);
entity.on("removehierarchy", this.onRemoveChild, this);
entity.on("insert", this.onInsertChild, this);
entity.on("inserthierarchy", this.onInsertChild, this);
}
set customAabb(value) {
this._customAabb = value;
this._instance?.meshInstance?.setCustomAabb(this._customAabb);
if (this._placement) {
this._placement.aabb = this._customAabb;
}
}
get customAabb() {
return this._customAabb ?? this._placement?.aabb ?? this.resource?.aabb ?? null;
}
set instance(value) {
if (this.unified) {
return;
}
this.destroyInstance();
this._instance = value;
if (this._instance) {
const mi = this._instance.meshInstance;
if (!mi.node) {
mi.node = this.entity;
}
mi.castShadow = this._castShadows;
mi.setCustomAabb(this._customAabb);
if (this.enabled && this.entity.enabled) {
this.addToLayers();
}
}
}
get instance() {
if (this.unified) {
}
return this._instance;
}
set material(value) {
if (this.unified) {
return;
}
if (this._instance) {
this._instance.material = value;
} else {
this._materialTmp = value;
}
}
get material() {
if (this.unified) {
return null;
}
return this._instance?.material ?? this._materialTmp ?? null;
}
set highQualitySH(value) {
if (value !== this._highQualitySH) {
this._highQualitySH = value;
this._instance?.setHighQualitySH(value);
}
}
get highQualitySH() {
return this._highQualitySH;
}
set castShadows(value) {
if (this._castShadows !== value) {
const layers = this.layers;
const scene = this.system.app.scene;
if (this._placement) {
if (value) {
for (let i = 0; i < layers.length; i++) {
const layer = scene.layers.getLayerById(layers[i]);
layer?.addGSplatShadowCaster(this._placement);
}
} else {
for (let i = 0; i < layers.length; i++) {
const layer = scene.layers.getLayerById(layers[i]);
layer?.removeGSplatShadowCaster(this._placement);
}
}
}
const mi = this._instance?.meshInstance;
if (mi) {
if (this._castShadows && !value) {
for (let i = 0; i < layers.length; i++) {
const layer = scene.layers.getLayerById(this.layers[i]);
layer?.removeShadowCasters([mi]);
}
}
mi.castShadow = value;
if (!this._castShadows && value) {
for (let i = 0; i < layers.length; i++) {
const layer = scene.layers.getLayerById(layers[i]);
layer?.addShadowCasters([mi]);
}
}
}
this._castShadows = value;
}
}
get castShadows() {
return this._castShadows;
}
set lodBaseDistance(value) {
this._lodBaseDistance = Math.max(0.1, value);
if (this._placement) {
this._placement.lodBaseDistance = this._lodBaseDistance;
}
}
get lodBaseDistance() {
return this._lodBaseDistance;
}
set lodMultiplier(value) {
this._lodMultiplier = Math.max(1.2, value);
if (this._placement) {
this._placement.lodMultiplier = this._lodMultiplier;
}
}
get lodMultiplier() {
return this._lodMultiplier;
}
set lodDistances(value) {
if (Array.isArray(value) && value.length > 0) {
this.lodBaseDistance = value[0];
this.lodMultiplier = 3;
}
}
get lodDistances() {
return [];
}
set splatBudget(value) {
}
get splatBudget() {
return 0;
}
set unified(value) {
if (value === false) {
}
if (this._unified !== value) {
this._unified = value;
this._onGSplatAssetAdded();
}
}
get unified() {
return this._unified;
}
get id() {
return this._id;
}
set workBufferUpdate(value) {
this._workBufferUpdate = value;
if (this._placement) {
this._placement.workBufferUpdate = value;
}
}
get workBufferUpdate() {
return this._workBufferUpdate;
}
setWorkBufferModifier(value) {
if (value) {
const device = this.system.app.graphicsDevice;
const code = (device.isWebGPU ? value.wgsl : value.glsl) ?? null;
this._workBufferModifier = code ? { code, hash: hashCode(code) } : null;
} else {
this._workBufferModifier = null;
}
if (this._placement) {
this._placement.workBufferModifier = this._workBufferModifier;
}
}
set layers(value) {
this.removeFromLayers();
this._layers.length = 0;
for (let i = 0; i < value.length; i++) {
this._layers[i] = value[i];
}
if (!this.enabled || !this.entity.enabled) {
return;
}
this.addToLayers();
}
get layers() {
return this._layers;
}
set asset(value) {
const id = value instanceof Asset ? value.id : value;
if (this._assetReference.id === id) return;
if (this._assetReference.asset && this._assetReference.asset.resource) {
this._onGSplatAssetRemove();
}
this._assetReference.id = id;
if (this._assetReference.asset) {
this._onGSplatAssetAdded();
}
}
get asset() {
return this._assetReference.id;
}
set resource(value) {
if (this._resource === value) return;
if (this._resource || this._assetReference.asset?.resource) {
this._onGSplatAssetRemove();
}
if (value && this._assetReference.id) {
this._assetReference.id = null;
}
this._resource = value;
if (this._resource && this.enabled && this.entity.enabled) {
this._onGSplatAssetLoad();
}
}
get resource() {
return this._resource ?? this._assetReference.asset?.resource ?? null;
}
destroyInstance() {
if (this._placement) {
this.removeFromLayers();
this._placement.destroy();
this._placement = null;
}
if (this._instance) {
this.removeFromLayers();
this._instance?.destroy();
this._instance = null;
}
}
addToLayers() {
if (this._placement) {
const layers = this.system.app.scene.layers;
for (let i = 0; i < this._layers.length; i++) {
const layer = layers.getLayerById(this._layers[i]);
if (layer) {
layer.addGSplatPlacement(this._placement);
if (this._castShadows) {
layer.addGSplatShadowCaster(this._placement);
}
}
}
return;
}
const meshInstance = this._instance?.meshInstance;
if (meshInstance) {
const layers = this.system.app.scene.layers;
for (let i = 0; i < this._layers.length; i++) {
layers.getLayerById(this._layers[i])?.addMeshInstances([meshInstance]);
}
}
}
removeFromLayers() {
if (this._placement) {
const layers = this.system.app.scene.layers;
for (let i = 0; i < this._layers.length; i++) {
const layer = layers.getLayerById(this._layers[i]);
if (layer) {
layer.removeGSplatPlacement(this._placement);
layer.removeGSplatShadowCaster(this._placement);
}
}
return;
}
const meshInstance = this._instance?.meshInstance;
if (meshInstance) {
const layers = this.system.app.scene.layers;
for (let i = 0; i < this._layers.length; i++) {
layers.getLayerById(this._layers[i])?.removeMeshInstances([meshInstance]);
}
}
}
onRemoveChild() {
this.removeFromLayers();
}
onInsertChild() {
if (this.enabled && this.entity.enabled) {
if (this._instance || this._placement) {
this.addToLayers();
}
}
}
onRemove() {
this.destroyInstance();
this.asset = null;
this._assetReference.id = null;
this.entity.off("remove", this.onRemoveChild, this);
this.entity.off("insert", this.onInsertChild, this);
}
onLayersChanged(oldComp, newComp) {
this.addToLayers();
oldComp.off("add", this.onLayerAdded, this);
oldComp.off("remove", this.onLayerRemoved, this);
newComp.on("add", this.onLayerAdded, this);
newComp.on("remove", this.onLayerRemoved, this);
}
onLayerAdded(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this.unified) return;
if (this._instance) {
layer.addMeshInstances(this._instance.meshInstance);
}
}
onLayerRemoved(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this.unified) return;
if (this._instance) {
layer.removeMeshInstances(this._instance.meshInstance);
}
}
onEnable() {
const scene = this.system.app.scene;
const layers = scene.layers;
this._evtLayersChanged = scene.on("set:layers", this.onLayersChanged, this);
if (layers) {
this._evtLayerAdded = layers.on("add", this.onLayerAdded, this);
this._evtLayerRemoved = layers.on("remove", this.onLayerRemoved, this);
}
if (this._instance || this._placement) {
this.addToLayers();
} else if (this.asset) {
this._onGSplatAssetAdded();
} else if (this._resource) {
this._onGSplatAssetLoad();
}
}
onDisable() {
const scene = this.system.app.scene;
const layers = scene.layers;
this._evtLayersChanged?.off();
this._evtLayersChanged = null;
if (layers) {
this._evtLayerAdded?.off();
this._evtLayerAdded = null;
this._evtLayerRemoved?.off();
this._evtLayerRemoved = null;
}
this.removeFromLayers();
}
hide() {
if (this._instance) {
this._instance.meshInstance.visible = false;
}
}
show() {
if (this._instance) {
this._instance.meshInstance.visible = true;
}
}
setParameter(name, data) {
const scopeId = this.system.app.graphicsDevice.scope.resolve(name);
this._parameters.set(name, { scopeId, data });
if (this._placement) this._placement.renderDirty = true;
}
getParameter(name) {
return this._parameters.get(name)?.data;
}
deleteParameter(name) {
this._parameters.delete(name);
if (this._placement) this._placement.renderDirty = true;
}
getInstanceTexture(name) {
if (!this._placement) {
return null;
}
return this._placement.getInstanceTexture(name, this.system.app.graphicsDevice) ?? null;
}
_onGSplatAssetAdded() {
if (!this._assetReference.asset) {
return;
}
if (this._assetReference.asset.resource) {
this._onGSplatAssetLoad();
} else if (this.enabled && this.entity.enabled) {
this.system.app.assets.load(this._assetReference.asset);
}
}
_onGSplatAssetLoad() {
this.destroyInstance();
const resource = this._resource ?? this._assetReference.asset?.resource;
if (!resource) return;
if (this.unified) {
this._placement = null;
this._placement = new GSplatPlacement(resource, this.entity, 0, this._parameters, null, this._id);
this._placement.lodBaseDistance = this._lodBaseDistance;
this._placement.lodMultiplier = this._lodMultiplier;
this._placement.workBufferUpdate = this._workBufferUpdate;
this._placement.workBufferModifier = this._workBufferModifier;
if (this.enabled && this.entity.enabled) {
this.addToLayers();
}
} else {
this.instance = new GSplatInstance(resource, {
material: this._materialTmp,
highQualitySH: this._highQualitySH,
scene: this.system.app.scene
});
this._materialTmp = null;
}
}
_onGSplatAssetUnload() {
this.destroyInstance();
}
_onGSplatAssetRemove() {
this._onGSplatAssetUnload();
}
}
export {
GSplatComponent
};