UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

357 lines (354 loc) 13 kB
import { LAYERID_WORLD } from '../../../scene/constants.js'; import { Asset } from '../../asset/asset.js'; import { AssetReference } from '../../asset/asset-reference.js'; import { Component } from '../component.js'; /** * @import { BoundingBox } from '../../../core/shape/bounding-box.js' * @import { Entity } from '../../entity.js' * @import { EventHandle } from '../../../core/event-handle.js' * @import { GSplatComponentSystem } from './system.js' * @import { GSplatInstance } from '../../../scene/gsplat/gsplat-instance.js' * @import { Material } from '../../../scene/materials/material.js' * @import { SplatMaterialOptions } from '../../../scene/gsplat/gsplat-material.js' */ /** * The GSplatComponent enables an {@link Entity} to render 3D Gaussian Splats. Splats are always * loaded from {@link Asset}s rather than being created programmatically. The asset type is * `gsplat` which are in the `.ply` file format. * * You should never need to use the GSplatComponent constructor directly. To add an * GSplatComponent to an {@link Entity}, use {@link Entity#addComponent}: * * ```javascript * const entity = pc.Entity(); * entity.addComponent('gsplat', { * asset: asset * }); * ``` * * Once the GSplatComponent is added to the entity, you can access it via the `gsplat` property: * * ```javascript * entity.gsplat.customAabb = new pc.BoundingBox(new pc.Vec3(), new pc.Vec3(10, 10, 10)); * * console.log(entity.gsplat.customAabb); * ``` * * Relevant Engine API examples: * * - [Loading a Splat](https://playcanvas.github.io/#/loaders/gsplat) * - [Custom Splat Shaders](https://playcanvas.github.io/#/loaders/gsplat-many) * * @hideconstructor * @category Graphics */ class GSplatComponent extends Component { /** * Sets a custom object space bounding box for visibility culling of the attached gsplat. * * @type {BoundingBox|null} */ set customAabb(value) { var // set it on meshInstance _this__instance_meshInstance, _this__instance; this._customAabb = value; (_this__instance = this._instance) == null ? void 0 : (_this__instance_meshInstance = _this__instance.meshInstance) == null ? void 0 : _this__instance_meshInstance.setCustomAabb(this._customAabb); } /** * Gets the custom object space bounding box for visibility culling of the attached gsplat. * * @type {BoundingBox|null} */ get customAabb() { return this._customAabb; } /** * Sets a {@link GSplatInstance} on the component. If not set or loaded, it returns null. * * @type {GSplatInstance|null} * @ignore */ set instance(value) { var _this__instance; // destroy existing instance this.destroyInstance(); this._instance = value; if ((_this__instance = this._instance) == null ? void 0 : _this__instance.meshInstance) { // if mesh instance was created without a node, assign it here var mi = this._instance.meshInstance; if (!mi.node) { mi.node = this.entity; } mi.setCustomAabb(this._customAabb); // if we have custom shader options, apply them if (this._materialOptions) { this._instance.createMaterial(this._materialOptions); } if (this.enabled && this.entity.enabled) { this.addToLayers(); } } } /** * Gets the {@link GSplatInstance} on the component. * * @type {GSplatInstance|null} * @ignore */ get instance() { return this._instance; } set materialOptions(value) { this._materialOptions = Object.assign({}, value); // apply them on the instance if it exists if (this._instance) { this._instance.createMaterial(this._materialOptions); } } get materialOptions() { return this._materialOptions; } /** * Gets the material used to render the gsplat. * * @type {Material|undefined} */ get material() { var _this__instance; return (_this__instance = this._instance) == null ? void 0 : _this__instance.material; } /** * Sets an array of layer IDs ({@link Layer#id}) to which this gsplat should belong. Don't * push, pop, splice or modify this array. If you want to change it, set a new one instead. * * @type {number[]} */ set layers(value) { // remove the mesh instances from old layers this.removeFromLayers(); // set the layer list this._layers.length = 0; for(var i = 0; i < value.length; i++){ this._layers[i] = value[i]; } // don't add into layers until we're enabled if (!this.enabled || !this.entity.enabled) { return; } // add the mesh instance to new layers this.addToLayers(); } /** * Gets the array of layer IDs ({@link Layer#id}) to which this gsplat belongs. * * @type {number[]} */ get layers() { return this._layers; } /** * Sets the gsplat asset for this gsplat component. Can also be an asset id. * * @type {Asset|number} */ set asset(value) { var 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(); } } /** * Gets the gsplat asset id for this gsplat component. * * @type {Asset|number} */ get asset() { return this._assetReference.id; } /** * Assign asset id to the component, without updating the component with the new asset. * This can be used to assign the asset id to already fully created component. * * @param {Asset|number} asset - The gsplat asset or asset id to assign. * @ignore */ assignAsset(asset) { var id = asset instanceof Asset ? asset.id : asset; this._assetReference.id = id; } /** @private */ destroyInstance() { if (this._instance) { var _this__instance; this.removeFromLayers(); (_this__instance = this._instance) == null ? void 0 : _this__instance.destroy(); this._instance = null; } } /** @private */ addToLayers() { var _this_instance; var meshInstance = (_this_instance = this.instance) == null ? void 0 : _this_instance.meshInstance; if (meshInstance) { var layers = this.system.app.scene.layers; for(var i = 0; i < this._layers.length; i++){ var _layers_getLayerById; (_layers_getLayerById = layers.getLayerById(this._layers[i])) == null ? void 0 : _layers_getLayerById.addMeshInstances([ meshInstance ]); } } } removeFromLayers() { var _this_instance; var meshInstance = (_this_instance = this.instance) == null ? void 0 : _this_instance.meshInstance; if (meshInstance) { var layers = this.system.app.scene.layers; for(var i = 0; i < this._layers.length; i++){ var _layers_getLayerById; (_layers_getLayerById = layers.getLayerById(this._layers[i])) == null ? void 0 : _layers_getLayerById.removeMeshInstances([ meshInstance ]); } } } /** @private */ onRemoveChild() { this.removeFromLayers(); } /** @private */ onInsertChild() { if (this._instance && this.enabled && this.entity.enabled) { 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) { var index = this.layers.indexOf(layer.id); if (index < 0) return; if (this._instance) { layer.addMeshInstances(this._instance.meshInstance); } } onLayerRemoved(layer) { var index = this.layers.indexOf(layer.id); if (index < 0) return; if (this._instance) { layer.removeMeshInstances(this._instance.meshInstance); } } onEnable() { var scene = this.system.app.scene; var 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.addToLayers(); } else if (this.asset) { this._onGSplatAssetAdded(); } } onDisable() { var _this__evtLayersChanged; var scene = this.system.app.scene; var layers = scene.layers; (_this__evtLayersChanged = this._evtLayersChanged) == null ? void 0 : _this__evtLayersChanged.off(); this._evtLayersChanged = null; if (layers) { var _this__evtLayerAdded, _this__evtLayerRemoved; (_this__evtLayerAdded = this._evtLayerAdded) == null ? void 0 : _this__evtLayerAdded.off(); this._evtLayerAdded = null; (_this__evtLayerRemoved = this._evtLayerRemoved) == null ? void 0 : _this__evtLayerRemoved.off(); this._evtLayerRemoved = null; } this.removeFromLayers(); } /** * Stop rendering this component without removing its mesh instance from the scene hierarchy. */ hide() { if (this._instance) { this._instance.meshInstance.visible = false; } } /** * Enable rendering of the component if hidden using {@link GSplatComponent#hide}. */ show() { if (this._instance) { this._instance.meshInstance.visible = true; } } _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() { // remove existing instance this.destroyInstance(); // create new instance var asset = this._assetReference.asset; if (asset) { this.instance = asset.resource.createInstance(); } } _onGSplatAssetUnload() { // when unloading asset, only remove the instance this.destroyInstance(); } _onGSplatAssetRemove() { this._onGSplatAssetUnload(); } /** * Create a new GSplatComponent. * * @param {GSplatComponentSystem} system - The ComponentSystem that created this Component. * @param {Entity} entity - The Entity that this Component is attached to. */ constructor(system, entity){ super(system, entity), /** @private */ this._layers = [ LAYERID_WORLD ] // assign to the default world layer , /** * @type {GSplatInstance|null} * @private */ this._instance = null, /** * @type {BoundingBox|null} * @private */ this._customAabb = null, /** * @type {SplatMaterialOptions|null} * @private */ this._materialOptions = null, /** * @type {EventHandle|null} * @private */ this._evtLayersChanged = null, /** * @type {EventHandle|null} * @private */ this._evtLayerAdded = null, /** * @type {EventHandle|null} * @private */ this._evtLayerRemoved = null; // gsplat asset reference this._assetReference = new AssetReference('asset', this, system.app.assets, { add: this._onGSplatAssetAdded, load: this._onGSplatAssetLoad, remove: this._onGSplatAssetRemove, unload: this._onGSplatAssetUnload }, this); // handle events when the entity is directly (or indirectly as a child of sub-hierarchy) // added or removed from the parent entity.on('remove', this.onRemoveChild, this); entity.on('removehierarchy', this.onRemoveChild, this); entity.on('insert', this.onInsertChild, this); entity.on('inserthierarchy', this.onInsertChild, this); } } export { GSplatComponent };