playcanvas
Version:
PlayCanvas WebGL game engine
829 lines (826 loc) • 32.3 kB
JavaScript
import { Debug } from '../../../core/debug.js';
import { LAYERID_WORLD, RENDERSTYLE_SOLID } from '../../../scene/constants.js';
import { BatchGroup } from '../../../scene/batching/batch-group.js';
import { MeshInstance } from '../../../scene/mesh-instance.js';
import { MorphInstance } from '../../../scene/morph-instance.js';
import { getShapePrimitive } from '../../graphics/primitive-cache.js';
import { GraphNode } from '../../../scene/graph-node.js';
import { SkinInstanceCache } from '../../../scene/skin-instance-cache.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 { Material } from '../../../scene/materials/material.js'
* @import { RenderComponentSystem } from './system.js'
*/ /**
* The RenderComponent enables an {@link Entity} to render 3D meshes. The {@link type} property can
* be set to one of several predefined shapes (such as `box`, `sphere`, `cone` and so on).
* Alternatively, the component can be configured to manage an arbitrary array of
* {@link MeshInstance}s. These can either be created programmatically or loaded from an
* {@link Asset}.
*
* The {@link MeshInstance}s managed by this component are positioned, rotated, and scaled in world
* space by the world transformation matrix of the owner {@link Entity}. This world matrix is
* derived by combining the entity's local transformation (position, rotation, and scale) with the
* world transformation matrix of its parent entity in the scene hierarchy.
*
* You should never need to use the RenderComponent constructor directly. To add a RenderComponent
* to an Entity, use {@link Entity#addComponent}:
*
* ```javascript
* const entity = new pc.Entity();
* entity.addComponent('render', {
* type: 'box'
* });
* ```
*
* Once the RenderComponent is added to the entity, you can access it via the {@link Entity#render}
* property:
*
* ```javascript
* entity.render.type = 'capsule'; // Set the render component's type
*
* console.log(entity.render.type); // Get the render component's type and print it
* ```
*
* Relevant Engine API examples:
*
* - [Loading Render Assets](https://playcanvas.github.io/#/graphics/render-asset)
* - [Primitive Shapes](https://playcanvas.github.io/#/graphics/shapes)
* - [Spinning Cube](https://playcanvas.github.io/#/misc/hello-world)
*
* @category Graphics
*/ class RenderComponent extends Component {
/**
* Create a new RenderComponent.
*
* @param {RenderComponentSystem} system - The ComponentSystem that created this Component.
* @param {Entity} entity - The Entity that this Component is attached to.
*/ constructor(system, entity){
super(system, entity), /**
* @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
* @private
*/ this._type = 'asset', /** @private */ this._castShadows = true, /** @private */ this._receiveShadows = true, /** @private */ this._castShadowsLightmap = true, /** @private */ this._lightmapped = false, /** @private */ this._lightmapSizeMultiplier = 1, /**
* Mark meshes as non-movable (optimization).
*
* @type {boolean}
*/ this.isStatic = false, /** @private */ this._batchGroupId = -1, /** @private */ this._layers = [
LAYERID_WORLD
] // assign to the default world layer
, /** @private */ this._renderStyle = RENDERSTYLE_SOLID, /**
* @type {MeshInstance[]}
* @private
*/ this._meshInstances = [], /**
* @type {BoundingBox|null}
* @private
*/ this._customAabb = null, /**
* Used by lightmapper.
*
* @type {{x: number, y: number, z: number, uv: number}|null}
* @ignore
*/ this._area = null, /**
* @type {AssetReference[]}
* @private
*/ this._materialReferences = [], /**
* A reference to the entity to be used as the root bone for any skinned meshes that
* are rendered by this component.
*
* @type {Entity|null}
* @private
*/ this._rootBone = null, /**
* @type {EventHandle|null}
* @private
*/ this._evtLayersChanged = null, /**
* @type {EventHandle|null}
* @private
*/ this._evtLayerAdded = null, /**
* @type {EventHandle|null}
* @private
*/ this._evtLayerRemoved = null, /**
* @type {EventHandle|null}
* @private
*/ this._evtSetMeshes = null;
// the entity that represents the root bone if this render component has skinned meshes
// render asset reference
this._assetReference = new AssetReference('asset', this, system.app.assets, {
add: this._onRenderAssetAdded,
load: this._onRenderAssetLoad,
remove: this._onRenderAssetRemove,
unload: this._onRenderAssetUnload
}, this);
this._material = system.defaultMaterial;
// 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);
}
/**
* Sets the render style of this component's {@link MeshInstance}s. Can be:
*
* - {@link RENDERSTYLE_SOLID}
* - {@link RENDERSTYLE_WIREFRAME}
* - {@link RENDERSTYLE_POINTS}
*
* Defaults to {@link RENDERSTYLE_SOLID}.
*
* @type {number}
*/ set renderStyle(renderStyle) {
if (this._renderStyle !== renderStyle) {
this._renderStyle = renderStyle;
MeshInstance._prepareRenderStyleForArray(this._meshInstances, renderStyle);
}
}
/**
* Gets the render style of this component's {@link MeshInstance}s.
*
* @type {number}
*/ get renderStyle() {
return this._renderStyle;
}
/**
* Sets the custom object space bounding box that is used for visibility culling of attached
* mesh instances. This is an optimization, allowing an oversized bounding box to be specified
* for skinned characters in order to avoid per frame bounding box computations based on bone
* positions.
*
* @type {BoundingBox|null}
*/ set customAabb(value) {
this._customAabb = value;
// set it on meshInstances
const mi = this._meshInstances;
if (mi) {
for(let i = 0; i < mi.length; i++){
mi[i].setCustomAabb(this._customAabb);
}
}
}
/**
* Gets the custom object space bounding box that is used for visibility culling of attached
* mesh instances.
*
* @type {BoundingBox|null}
*/ get customAabb() {
return this._customAabb;
}
/**
* Sets the type of the component, determining the source of the geometry to be rendered.
* The geometry, whether it's a primitive shape or originates from an asset, is rendered
* using the owning entity's final world transform. This world transform is calculated by
* concatenating (multiplying) the local transforms (position, rotation, scale) of the
* entity and all its ancestors in the scene hierarchy. This process positions, orientates,
* and scales the geometry in world space.
*
* Can be one of the following values:
*
* - **"asset"**: Renders geometry defined in an {@link Asset} of type `render`. This asset,
* assigned to the {@link asset} property, contains one or more {@link MeshInstance}s.
* Alternatively, {@link meshInstances} can be set programmatically.
* - **"box"**: A unit cube (sides of length 1) centered at the local space origin.
* - **"capsule"**: A shape composed of a cylinder and two hemispherical caps that is aligned
* with the local Y-axis. It is centered at the local space origin and has an unscaled height
* of 2 and a radius of 0.5.
* - **"cone"**: A cone aligned with the local Y-axis. It is centered at the local space
* origin, with its base in the local XZ plane at Y = -0.5 and its tip at Y = +0.5. It has
* an unscaled height of 1 and a base radius of 0.5.
* - **"cylinder"**: A cylinder aligned with the local Y-axis. It is centered at the local
* space origin with an unscaled height of 1 and a radius of 0.5.
* - **"plane"**: A flat plane in the local XZ plane at Y = 0 (normal along +Y). It is
* centered at the local space origin with unscaled dimensions of 1x1 units along local X and
* Z axes.
* - **"sphere"**: A sphere with a radius of 0.5. It is centered at the local space origin and
* has poles at Y = -0.5 and Y = +0.5.
* - **"torus"**: A doughnut shape lying in the local XZ plane at Y = 0. It is centered at
* the local space origin with a tube radius of 0.2 and a ring radius of 0.3.
*
* @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
*/ set type(value) {
if (this._type !== value) {
this._area = null;
this._type = value;
this.destroyMeshInstances();
if (value !== 'asset') {
let material = this._material;
if (!material || material === this.system.defaultMaterial) {
material = this._materialReferences[0] && this._materialReferences[0].asset && this._materialReferences[0].asset.resource;
}
const primData = getShapePrimitive(this.system.app.graphicsDevice, value);
this._area = primData.area;
this.meshInstances = [
new MeshInstance(primData.mesh, material || this.system.defaultMaterial, this.entity)
];
}
}
}
/**
* Gets the type of the component.
*
* @type {'asset'|'box'|'capsule'|'cone'|'cylinder'|'plane'|'sphere'|'torus'}
*/ get type() {
return this._type;
}
/**
* Sets the array of meshInstances contained in the component.
*
* @type {MeshInstance[]}
*/ set meshInstances(value) {
Debug.assert(Array.isArray(value), 'MeshInstances set to a Render component must be an array.');
this.destroyMeshInstances();
this._meshInstances = value;
if (this._meshInstances) {
const mi = this._meshInstances;
for(let i = 0; i < mi.length; i++){
// if mesh instance was created without a node, assign it here
if (!mi[i].node) {
mi[i].node = this.entity;
}
mi[i].castShadow = this._castShadows;
mi[i].receiveShadow = this._receiveShadows;
mi[i].renderStyle = this._renderStyle;
mi[i].setLightmapped(this._lightmapped);
mi[i].setCustomAabb(this._customAabb);
}
if (this.enabled && this.entity.enabled) {
this.addToLayers();
}
}
}
/**
* Gets the array of meshInstances contained in the component.
*
* @type {MeshInstance[]}
*/ get meshInstances() {
return this._meshInstances;
}
/**
* Sets whether the component is affected by the runtime lightmapper. If true, the meshes will
* be lightmapped after using lightmapper.bake().
*
* @type {boolean}
*/ set lightmapped(value) {
if (value !== this._lightmapped) {
this._lightmapped = value;
const mi = this._meshInstances;
if (mi) {
for(let i = 0; i < mi.length; i++){
mi[i].setLightmapped(value);
}
}
}
}
/**
* Gets whether the component is affected by the runtime lightmapper.
*
* @type {boolean}
*/ get lightmapped() {
return this._lightmapped;
}
/**
* Sets whether attached meshes will cast shadows for lights that have shadow casting enabled.
*
* @type {boolean}
*/ set castShadows(value) {
if (this._castShadows !== value) {
const mi = this._meshInstances;
if (mi) {
const layers = this.layers;
const scene = this.system.app.scene;
if (this._castShadows && !value) {
for(let i = 0; i < layers.length; i++){
const layer = scene.layers.getLayerById(this.layers[i]);
if (layer) {
layer.removeShadowCasters(mi);
}
}
}
for(let i = 0; i < mi.length; i++){
mi[i].castShadow = value;
}
if (!this._castShadows && value) {
for(let i = 0; i < layers.length; i++){
const layer = scene.layers.getLayerById(layers[i]);
if (layer) {
layer.addShadowCasters(mi);
}
}
}
}
this._castShadows = value;
}
}
/**
* Gets whether attached meshes will cast shadows for lights that have shadow casting enabled.
*
* @type {boolean}
*/ get castShadows() {
return this._castShadows;
}
/**
* Sets whether shadows will be cast on attached meshes.
*
* @type {boolean}
*/ set receiveShadows(value) {
if (this._receiveShadows !== value) {
this._receiveShadows = value;
const mi = this._meshInstances;
if (mi) {
for(let i = 0; i < mi.length; i++){
mi[i].receiveShadow = value;
}
}
}
}
/**
* Gets whether shadows will be cast on attached meshes.
*
* @type {boolean}
*/ get receiveShadows() {
return this._receiveShadows;
}
/**
* Sets whether meshes instances will cast shadows when rendering lightmaps.
*
* @type {boolean}
*/ set castShadowsLightmap(value) {
this._castShadowsLightmap = value;
}
/**
* Gets whether meshes instances will cast shadows when rendering lightmaps.
*
* @type {boolean}
*/ get castShadowsLightmap() {
return this._castShadowsLightmap;
}
/**
* Sets the lightmap resolution multiplier.
*
* @type {number}
*/ set lightmapSizeMultiplier(value) {
this._lightmapSizeMultiplier = value;
}
/**
* Gets the lightmap resolution multiplier.
*
* @type {number}
*/ get lightmapSizeMultiplier() {
return this._lightmapSizeMultiplier;
}
/**
* Sets the array of layer IDs ({@link Layer#id}) to which the mesh instances 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) {
const layers = this.system.app.scene.layers;
let layer;
if (this._meshInstances) {
// remove all mesh instances from old layers
for(let i = 0; i < this._layers.length; i++){
layer = layers.getLayerById(this._layers[i]);
if (layer) {
layer.removeMeshInstances(this._meshInstances);
}
}
}
// set the layer list
this._layers.length = 0;
for(let 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 || !this._meshInstances) return;
// add all mesh instances to new layers
for(let i = 0; i < this._layers.length; i++){
layer = layers.getLayerById(this._layers[i]);
if (layer) {
layer.addMeshInstances(this._meshInstances);
}
}
}
/**
* Gets the array of layer IDs ({@link Layer#id}) to which the mesh instances belong.
*
* @type {number[]}
*/ get layers() {
return this._layers;
}
/**
* Sets the batch group for the mesh instances in this component (see {@link BatchGroup}).
* Default is -1 (no group).
*
* @type {number}
*/ set batchGroupId(value) {
if (this._batchGroupId !== value) {
if (this.entity.enabled && this._batchGroupId >= 0) {
this.system.app.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);
}
if (this.entity.enabled && value >= 0) {
this.system.app.batcher?.insert(BatchGroup.RENDER, value, this.entity);
}
if (value < 0 && this._batchGroupId >= 0 && this.enabled && this.entity.enabled) {
// re-add render to scene, in case it was removed by batching
this.addToLayers();
}
this._batchGroupId = value;
}
}
/**
* Gets the batch group for the mesh instances in this component (see {@link BatchGroup}).
*
* @type {number}
*/ get batchGroupId() {
return this._batchGroupId;
}
/**
* Sets the material {@link Material} that will be used to render the component. The material
* is ignored for renders of type 'asset'.
*
* @type {Material}
*/ set material(value) {
if (this._material !== value) {
this._material = value;
if (this._meshInstances && this._type !== 'asset') {
for(let i = 0; i < this._meshInstances.length; i++){
this._meshInstances[i].material = value;
}
}
}
}
/**
* Gets the material {@link Material} that will be used to render the component.
*
* @type {Material}
*/ get material() {
return this._material;
}
/**
* Sets the material assets that will be used to render the component. Each material
* corresponds to the respective mesh instance.
*
* @type {Asset[]|number[]}
*/ set materialAssets(value = []) {
if (this._materialReferences.length > value.length) {
for(let i = value.length; i < this._materialReferences.length; i++){
this._materialReferences[i].id = null;
}
this._materialReferences.length = value.length;
}
for(let i = 0; i < value.length; i++){
if (!this._materialReferences[i]) {
this._materialReferences.push(new AssetReference(i, this, this.system.app.assets, {
add: this._onMaterialAdded,
load: this._onMaterialLoad,
remove: this._onMaterialRemove,
unload: this._onMaterialUnload
}, this));
}
if (value[i]) {
const id = value[i] instanceof Asset ? value[i].id : value[i];
if (this._materialReferences[i].id !== id) {
this._materialReferences[i].id = id;
}
if (this._materialReferences[i].asset) {
this._onMaterialAdded(i, this, this._materialReferences[i].asset);
}
} else {
this._materialReferences[i].id = null;
if (this._meshInstances[i]) {
this._meshInstances[i].material = this.system.defaultMaterial;
}
}
}
}
/**
* Gets the material assets that will be used to render the component.
*
* @type {Asset[]|number[]}
*/ get materialAssets() {
return this._materialReferences.map((ref)=>{
return ref.id;
});
}
/**
* Sets the render asset (or asset id) for the render component. This only applies to render components with
* type 'asset'.
*
* @type {Asset|number}
*/ 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._onRenderAssetRemove();
}
this._assetReference.id = id;
if (this._assetReference.asset) {
this._onRenderAssetAdded();
}
}
/**
* Gets the render asset id for the render component.
*
* @type {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 render asset or asset id to assign.
* @ignore
*/ assignAsset(asset) {
const id = asset instanceof Asset ? asset.id : asset;
this._assetReference.id = id;
}
/**
* Sets the root bone entity (or entity guid) for the render component.
*
* @type {Entity|string|null}
*/ set rootBone(value) {
if (this._rootBone !== value) {
const isString = typeof value === 'string';
if (this._rootBone && isString && this._rootBone.getGuid() === value) {
return;
}
if (this._rootBone) {
this._clearSkinInstances();
}
if (value instanceof GraphNode) {
this._rootBone = value;
} else if (isString) {
this._rootBone = this.system.app.getEntityFromIndex(value) || null;
if (!this._rootBone) {
Debug.warn('Failed to find rootBone Entity by GUID');
}
} else {
this._rootBone = null;
}
if (this._rootBone) {
this._cloneSkinInstances();
}
}
}
/**
* Gets the root bone entity for the render component.
*
* @type {Entity|null}
*/ get rootBone() {
return this._rootBone;
}
/** @private */ destroyMeshInstances() {
const meshInstances = this._meshInstances;
if (meshInstances) {
this.removeFromLayers();
// destroy mesh instances separately to allow them to be removed from the cache
this._clearSkinInstances();
for(let i = 0; i < meshInstances.length; i++){
meshInstances[i].destroy();
}
this._meshInstances.length = 0;
}
}
/** @private */ addToLayers() {
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.addMeshInstances(this._meshInstances);
}
}
}
removeFromLayers() {
if (this._meshInstances && this._meshInstances.length) {
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.removeMeshInstances(this._meshInstances);
}
}
}
}
/** @private */ onRemoveChild() {
this.removeFromLayers();
}
/** @private */ onInsertChild() {
if (this._meshInstances && this.enabled && this.entity.enabled) {
this.addToLayers();
}
}
onRemove() {
this.destroyMeshInstances();
this.asset = null;
this.materialAsset = null;
this._assetReference.id = null;
for(let i = 0; i < this._materialReferences.length; i++){
this._materialReferences[i].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;
layer.addMeshInstances(this._meshInstances);
}
onLayerRemoved(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
layer.removeMeshInstances(this._meshInstances);
}
onEnable() {
const app = this.system.app;
const scene = app.scene;
const layers = scene.layers;
if (this._rootBone) {
this._cloneSkinInstances();
}
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);
}
const isAsset = this._type === 'asset';
if (this._meshInstances && this._meshInstances.length) {
this.addToLayers();
} else if (isAsset && this.asset) {
this._onRenderAssetAdded();
}
// load materials
for(let i = 0; i < this._materialReferences.length; i++){
if (this._materialReferences[i].asset) {
this.system.app.assets.load(this._materialReferences[i].asset);
}
}
if (this._batchGroupId >= 0) {
app.batcher?.insert(BatchGroup.RENDER, this.batchGroupId, this.entity);
}
}
onDisable() {
const app = this.system.app;
const scene = app.scene;
const layers = scene.layers;
this._evtLayersChanged?.off();
this._evtLayersChanged = null;
if (this._rootBone) {
this._clearSkinInstances();
}
if (layers) {
this._evtLayerAdded?.off();
this._evtLayerAdded = null;
this._evtLayerRemoved?.off();
this._evtLayerRemoved = null;
}
if (this._batchGroupId >= 0) {
app.batcher?.remove(BatchGroup.RENDER, this.batchGroupId, this.entity);
}
this.removeFromLayers();
}
/**
* Stop rendering {@link MeshInstance}s without removing them from the scene hierarchy. This
* method sets the {@link MeshInstance#visible} property of every MeshInstance to false. Note,
* this does not remove the mesh instances from the scene hierarchy or draw call list. So the
* render component still incurs some CPU overhead.
*/ hide() {
if (this._meshInstances) {
for(let i = 0; i < this._meshInstances.length; i++){
this._meshInstances[i].visible = false;
}
}
}
/**
* Enable rendering of the component's {@link MeshInstance}s if hidden using
* {@link RenderComponent#hide}. This method sets the {@link MeshInstance#visible} property on
* all mesh instances to true.
*/ show() {
if (this._meshInstances) {
for(let i = 0; i < this._meshInstances.length; i++){
this._meshInstances[i].visible = true;
}
}
}
_onRenderAssetAdded() {
if (!this._assetReference.asset) return;
if (this._assetReference.asset.resource) {
this._onRenderAssetLoad();
} else if (this.enabled && this.entity.enabled) {
this.system.app.assets.load(this._assetReference.asset);
}
}
_onRenderAssetLoad() {
// remove existing instances
this.destroyMeshInstances();
if (this._assetReference.asset) {
const render = this._assetReference.asset.resource;
this._evtSetMeshes?.off();
this._evtSetMeshes = render.on('set:meshes', this._onSetMeshes, this);
if (render.meshes) {
this._onSetMeshes(render.meshes);
}
}
}
_onSetMeshes(meshes) {
this._cloneMeshes(meshes);
}
_clearSkinInstances() {
for(let i = 0; i < this._meshInstances.length; i++){
const meshInstance = this._meshInstances[i];
// remove it from the cache
SkinInstanceCache.removeCachedSkinInstance(meshInstance.skinInstance);
meshInstance.skinInstance = null;
}
}
_cloneSkinInstances() {
if (this._meshInstances.length && this._rootBone instanceof GraphNode) {
for(let i = 0; i < this._meshInstances.length; i++){
const meshInstance = this._meshInstances[i];
const mesh = meshInstance.mesh;
// if skinned but does not have instance created yet
if (mesh.skin && !meshInstance.skinInstance) {
meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(mesh.skin, this._rootBone, this.entity);
}
}
}
}
_cloneMeshes(meshes) {
if (meshes && meshes.length) {
// cloned mesh instances
const meshInstances = [];
for(let i = 0; i < meshes.length; i++){
// mesh instance
const mesh = meshes[i];
const material = this._materialReferences[i] && this._materialReferences[i].asset && this._materialReferences[i].asset.resource;
const meshInst = new MeshInstance(mesh, material || this.system.defaultMaterial, this.entity);
meshInstances.push(meshInst);
// morph instance
if (mesh.morph) {
meshInst.morphInstance = new MorphInstance(mesh.morph);
}
}
this.meshInstances = meshInstances;
// try to create skin instances if rootBone has been set, otherwise this executes when rootBone is set later
this._cloneSkinInstances();
}
}
_onRenderAssetUnload() {
// when unloading asset, only remove asset mesh instances (type could have been already changed to 'box' or similar)
if (this._type === 'asset') {
this.destroyMeshInstances();
}
}
_onRenderAssetRemove() {
this._evtSetMeshes?.off();
this._evtSetMeshes = null;
this._onRenderAssetUnload();
}
_onMaterialAdded(index, component, asset) {
if (asset.resource) {
this._onMaterialLoad(index, component, asset);
} else {
if (this.enabled && this.entity.enabled) {
this.system.app.assets.load(asset);
}
}
}
_updateMainMaterial(index, material) {
// first material for primitives can be accessed using material property, so set it up
if (index === 0) {
this.material = material;
}
}
_onMaterialLoad(index, component, asset) {
if (this._meshInstances[index]) {
this._meshInstances[index].material = asset.resource;
}
this._updateMainMaterial(index, asset.resource);
}
_onMaterialRemove(index, component, asset) {
if (this._meshInstances[index]) {
this._meshInstances[index].material = this.system.defaultMaterial;
}
this._updateMainMaterial(index, this.system.defaultMaterial);
}
_onMaterialUnload(index, component, asset) {
if (this._meshInstances[index]) {
this._meshInstances[index].material = this.system.defaultMaterial;
}
this._updateMainMaterial(index, this.system.defaultMaterial);
}
resolveDuplicatedEntityReferenceProperties(oldRender, duplicatedIdsMap) {
if (oldRender.rootBone) {
this.rootBone = duplicatedIdsMap[oldRender.rootBone.getGuid()];
}
}
}
export { RenderComponent };