@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
942 lines (864 loc) • 30.3 kB
JavaScript
import { Debug, DebugHelper } from '../core/debug.js';
import { BoundingBox } from '../core/shape/bounding-box.js';
import { BoundingSphere } from '../core/shape/bounding-sphere.js';
import { BindGroup } from '../platform/graphics/bind-group.js';
import { UniformBuffer } from '../platform/graphics/uniform-buffer.js';
import { MASK_AFFECT_DYNAMIC, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADERDEF_TANGENTS, LAYER_WORLD, RENDERSTYLE_SOLID, SHADERDEF_NOSHADOW, SHADERDEF_SKIN, SHADERDEF_MORPH_TEXTURE_BASED, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_SCREENSPACE, SORTKEY_FORWARD, BLEND_NORMAL, BLEND_NONE, SHADERDEF_INSTANCING, MASK_AFFECT_LIGHTMAPPED, MASK_BAKE, SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT } from './constants.js';
import { GraphNode } from './graph-node.js';
import { getDefaultMaterial } from './materials/default-material.js';
import { LightmapCache } from './graphics/lightmap-cache.js';
import { DebugGraphics } from '../platform/graphics/debug-graphics.js';
let id = 0;
const _tmpAabb = new BoundingBox();
const _tempBoneAabb = new BoundingBox();
const _tempSphere = new BoundingSphere();
const _meshSet = new Set();
/**
* Internal data structure used to store data used by hardware instancing.
*
* @ignore
*/
class InstancingData {
/**
* @param {number} numObjects - The number of objects instanced.
*/
constructor(numObjects) {
/** @type {import('../platform/graphics/vertex-buffer.js').VertexBuffer|null} */
this.vertexBuffer = null;
this.count = numObjects;
}
}
/**
* Internal helper class for storing the shader and related mesh bind group in the shader cache.
*
* @ignore
*/
class ShaderInstance {
constructor() {
/**
* A shader.
*
* @type {import('../platform/graphics/shader.js').Shader|undefined}
*/
this.shader = void 0;
/**
* A bind group storing mesh uniforms for the shader.
*
* @type {BindGroup|null}
*/
this.bindGroup = null;
}
/**
* Returns the mesh bind group for the shader.
*
* @param {import('../platform/graphics/graphics-device.js').GraphicsDevice} device - The
* graphics device.
* @returns {BindGroup} - The mesh bind group.
*/
getBindGroup(device) {
// create bind group
if (!this.bindGroup) {
const shader = this.shader;
Debug.assert(shader);
// mesh uniform buffer
const ubFormat = shader.meshUniformBufferFormat;
Debug.assert(ubFormat);
const uniformBuffer = new UniformBuffer(device, ubFormat, false);
// mesh bind group
const bindGroupFormat = shader.meshBindGroupFormat;
Debug.assert(bindGroupFormat);
this.bindGroup = new BindGroup(device, bindGroupFormat, uniformBuffer);
DebugHelper.setName(this.bindGroup, `MeshBindGroup_${this.bindGroup.id}`);
}
return this.bindGroup;
}
destroy() {
const group = this.bindGroup;
if (group) {
var _group$defaultUniform;
(_group$defaultUniform = group.defaultUniformBuffer) == null || _group$defaultUniform.destroy();
group.destroy();
this.bindGroup = null;
}
}
}
/**
* An entry in the shader cache, representing shaders for this mesh instance and a specific shader
* pass.
*
* @ignore
*/
class ShaderCacheEntry {
constructor() {
/**
* The shader instances. Looked up by lightHash, which represents an ordered set of lights.
*
* @type {Map<number, ShaderInstance>}
*/
this.shaderInstances = new Map();
}
destroy() {
this.shaderInstances.forEach(instance => instance.destroy());
this.shaderInstances.clear();
}
}
/**
* Callback used by {@link Layer} to calculate the "sort distance" for a {@link MeshInstance},
* which determines its place in the render order.
*
* @callback CalculateSortDistanceCallback
* @param {MeshInstance} meshInstance - The mesh instance.
* @param {import('../core/math/vec3.js').Vec3} cameraPosition - The position of the camera.
* @param {import('../core/math/vec3.js').Vec3} cameraForward - The forward vector of the camera.
*/
/**
* An instance of a {@link Mesh}. A single mesh can be referenced by many mesh instances that can
* have different transforms and materials.
*
* @category Graphics
*/
class MeshInstance {
/**
* Create a new MeshInstance instance.
*
* @param {import('./mesh.js').Mesh} mesh - The graphics mesh to instance.
* @param {import('./materials/material.js').Material} material - The material to use for this
* mesh instance.
* @param {GraphNode} [node] - The graph node defining the transform for this instance. This
* parameter is optional when used with {@link RenderComponent} and will use the node the
* component is attached to.
* @example
* // Create a mesh instance pointing to a 1x1x1 'cube' mesh
* const mesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.BoxGeometry());
* const material = new pc.StandardMaterial();
*
* const meshInstance = new pc.MeshInstance(mesh, material);
*
* const entity = new pc.Entity();
* entity.addComponent('render', {
* meshInstances: [meshInstance]
* });
*
* // Add the entity to the scene hierarchy
* this.app.scene.root.addChild(entity);
*/
constructor(mesh, material, node = null) {
/**
* Enable rendering for this mesh instance. Use visible property to enable/disable
* rendering without overhead of removing from scene. But note that the mesh instance is
* still in the hierarchy and still in the draw call list.
*
* @type {boolean}
*/
this.visible = true;
/**
* Enable shadow casting for this mesh instance. Use this property to enable/disable
* shadow casting without overhead of removing from scene. Note that this property does not
* add the mesh instance to appropriate list of shadow casters on a {@link Layer}, but
* allows mesh to be skipped from shadow casting while it is in the list already. Defaults to
* false.
*
* @type {boolean}
*/
this.castShadow = false;
/**
* True if the material of the mesh instance is transparent. Optimization to avoid accessing the
* material. Updated by the material instance itself.
*
* @ignore
*/
this.transparent = false;
/**
* @type {import('./materials/material.js').Material|null}
* @private
*/
this._material = null;
/**
* An array of shader cache entries, indexed by the shader pass constant (SHADER_FORWARD..). The
* value stores all shaders and bind groups for the shader pass for various light combinations.
*
* @type {Array<ShaderCacheEntry|null>}
* @private
*/
this._shaderCache = [];
/** @ignore */
this.id = id++;
/**
* True if the mesh instance is pickable by the {@link Picker}. Defaults to true.
*
* @type {boolean}
* @ignore
*/
this.pick = true;
// if first parameter is of GraphNode type, handle previous constructor signature: (node, mesh, material)
if (mesh instanceof GraphNode) {
const temp = mesh;
mesh = material;
material = node;
node = temp;
}
this._key = [0, 0];
/**
* The graph node defining the transform for this instance.
*
* @type {GraphNode}
*/
this.node = node; // The node that defines the transform of the mesh instance
this._mesh = mesh; // The mesh that this instance renders
mesh.incRefCount();
this.material = material; // The material with which to render this instance
this._shaderDefs = MASK_AFFECT_DYNAMIC << 16; // 2 byte toggles, 2 bytes light mask; Default value is no toggles and mask = pc.MASK_AFFECT_DYNAMIC
if (mesh.vertexBuffer) {
const format = mesh.vertexBuffer.format;
this._shaderDefs |= format.hasUv0 ? SHADERDEF_UV0 : 0;
this._shaderDefs |= format.hasUv1 ? SHADERDEF_UV1 : 0;
this._shaderDefs |= format.hasColor ? SHADERDEF_VCOLOR : 0;
this._shaderDefs |= format.hasTangents ? SHADERDEF_TANGENTS : 0;
}
// Render options
this.layer = LAYER_WORLD; // legacy
/** @private */
this._renderStyle = RENDERSTYLE_SOLID;
this._receiveShadow = true;
this._screenSpace = false;
/**
* Controls whether the mesh instance can be culled by frustum culling
* ({@link CameraComponent#frustumCulling}). Defaults to true.
*
* @type {boolean}
*/
this.cull = true;
this._updateAabb = true;
this._updateAabbFunc = null;
this._calculateSortDistance = null;
// 64-bit integer key that defines render order of this mesh instance
this.updateKey();
/**
* @type {import('./skin-instance.js').SkinInstance|null}
* @private
*/
this._skinInstance = null;
/**
* @type {import('./morph-instance.js').MorphInstance|null}
* @private
*/
this._morphInstance = null;
/**
* @type {import('./gsplat/gsplat-instance.js').GSplatInstance|null}
* @ignore
*/
this.gsplatInstance = null;
this.instancingData = null;
/**
* @type {BoundingBox|null}
* @private
*/
this._customAabb = null;
// World space AABB
this.aabb = new BoundingBox();
this._aabbVer = -1;
this._aabbMeshVer = -1;
/**
* Use this value to affect rendering order of mesh instances. Only used when mesh
* instances are added to a {@link Layer} with {@link Layer#opaqueSortMode} or
* {@link Layer#transparentSortMode} (depending on the material) set to
* {@link SORTMODE_MANUAL}.
*
* @type {number}
*/
this.drawOrder = 0;
/**
* Read this value in {@link Layer#onPostCull} to determine if the object is actually going
* to be rendered.
*
* @type {boolean}
*/
this.visibleThisFrame = false;
// custom function used to customize culling (e.g. for 2D UI elements)
this.isVisibleFunc = null;
this.parameters = {};
this.stencilFront = null;
this.stencilBack = null;
// Negative scale batching support
this.flipFacesFactor = 1;
}
/**
* Sets the render style of the mesh instance. Can be:
*
* - {@link RENDERSTYLE_SOLID}
* - {@link RENDERSTYLE_WIREFRAME}
* - {@link RENDERSTYLE_POINTS}
*
* Defaults to {@link RENDERSTYLE_SOLID}.
*
* @type {number}
*/
set renderStyle(renderStyle) {
this._renderStyle = renderStyle;
this.mesh.prepareRenderState(renderStyle);
}
/**
* Gets the render style of the mesh instance.
*
* @type {number}
*/
get renderStyle() {
return this._renderStyle;
}
/**
* Sets the graphics mesh being instanced.
*
* @type {import('./mesh.js').Mesh}
*/
set mesh(mesh) {
if (mesh === this._mesh) {
return;
}
if (this._mesh) {
this._mesh.decRefCount();
}
this._mesh = mesh;
if (mesh) {
mesh.incRefCount();
}
}
/**
* Gets the graphics mesh being instanced.
*
* @type {import('./mesh.js').Mesh}
*/
get mesh() {
return this._mesh;
}
/**
* Sets the world space axis-aligned bounding box for this mesh instance.
*
* @type {BoundingBox}
*/
set aabb(aabb) {
this._aabb = aabb;
}
/**
* Gets the world space axis-aligned bounding box for this mesh instance.
*
* @type {BoundingBox}
*/
get aabb() {
// use specified world space aabb
if (!this._updateAabb) {
return this._aabb;
}
// callback function returning world space aabb
if (this._updateAabbFunc) {
return this._updateAabbFunc(this._aabb);
}
// use local space override aabb if specified
let localAabb = this._customAabb;
let toWorldSpace = !!localAabb;
// otherwise evaluate local aabb
if (!localAabb) {
localAabb = _tmpAabb;
if (this.skinInstance) {
// Initialize local bone AABBs if needed
if (!this.mesh.boneAabb) {
const morphTargets = this._morphInstance ? this._morphInstance.morph._targets : null;
this.mesh._initBoneAabbs(morphTargets);
}
// evaluate local space bounds based on all active bones
const boneUsed = this.mesh.boneUsed;
let first = true;
for (let i = 0; i < this.mesh.boneAabb.length; i++) {
if (boneUsed[i]) {
// transform bone AABB by bone matrix
_tempBoneAabb.setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
// add them up
if (first) {
first = false;
localAabb.center.copy(_tempBoneAabb.center);
localAabb.halfExtents.copy(_tempBoneAabb.halfExtents);
} else {
localAabb.add(_tempBoneAabb);
}
}
}
toWorldSpace = true;
} else if (this.node._aabbVer !== this._aabbVer || this.mesh._aabbVer !== this._aabbMeshVer) {
// local space bounding box - either from mesh or empty
if (this.mesh) {
localAabb.center.copy(this.mesh.aabb.center);
localAabb.halfExtents.copy(this.mesh.aabb.halfExtents);
} else {
localAabb.center.set(0, 0, 0);
localAabb.halfExtents.set(0, 0, 0);
}
// update local space bounding box by morph targets
if (this.mesh && this.mesh.morph) {
const morphAabb = this.mesh.morph.aabb;
localAabb._expand(morphAabb.getMin(), morphAabb.getMax());
}
toWorldSpace = true;
this._aabbVer = this.node._aabbVer;
this._aabbMeshVer = this.mesh._aabbVer;
}
}
// store world space bounding box
if (toWorldSpace) {
this._aabb.setFromTransformedAabb(localAabb, this.node.getWorldTransform());
}
return this._aabb;
}
/**
* Clear the internal shader cache.
*
* @ignore
*/
clearShaders() {
const shaderCache = this._shaderCache;
for (let i = 0; i < shaderCache.length; i++) {
var _shaderCache$i;
(_shaderCache$i = shaderCache[i]) == null || _shaderCache$i.destroy();
shaderCache[i] = null;
}
}
/**
* Returns the shader instance for the specified shader pass and light hash that is compatible
* with this mesh instance.
*
* @param {number} shaderPass - The shader pass index.
* @param {number} lightHash - The hash value of the lights that are affecting this mesh instance.
* @param {import('./scene.js').Scene} scene - The scene.
* @param {import('../platform/graphics/uniform-buffer-format.js').UniformBufferFormat} [viewUniformFormat] - The
* format of the view uniform buffer.
* @param {import('../platform/graphics/bind-group-format.js').BindGroupFormat} [viewBindGroupFormat] - The
* format of the view bind group.
* @param {any} [sortedLights] - Array of arrays of lights.
* @returns {ShaderInstance} - the shader instance.
* @ignore
*/
getShaderInstance(shaderPass, lightHash, scene, viewUniformFormat, viewBindGroupFormat, sortedLights) {
let shaderInstance;
let passEntry = this._shaderCache[shaderPass];
if (passEntry) {
shaderInstance = passEntry.shaderInstances.get(lightHash);
} else {
passEntry = new ShaderCacheEntry();
this._shaderCache[shaderPass] = passEntry;
}
// cache miss in the shader cache of the mesh instance
if (!shaderInstance) {
// get the shader from the material
const mat = this._material;
const shaderDefs = this._shaderDefs;
const variantKey = `${shaderPass}_${shaderDefs}_${lightHash}`;
shaderInstance = new ShaderInstance();
shaderInstance.shader = mat.variants.get(variantKey);
// cache miss in the material variants
if (!shaderInstance.shader) {
var _this$_mesh$vertexBuf;
// marker to allow us to see the source node for shader alloc
DebugGraphics.pushGpuMarker(this.mesh.device, `Node: ${this.node.name}`);
const shader = mat.getShaderVariant(this.mesh.device, scene, shaderDefs, null, shaderPass, sortedLights, viewUniformFormat, viewBindGroupFormat, (_this$_mesh$vertexBuf = this._mesh.vertexBuffer) == null ? void 0 : _this$_mesh$vertexBuf.format);
DebugGraphics.popGpuMarker(this.mesh.device);
// add it to the material variants cache
mat.variants.set(variantKey, shader);
shaderInstance.shader = shader;
}
// add it to the mesh instance cache
passEntry.shaderInstances.set(lightHash, shaderInstance);
}
return shaderInstance;
}
/**
* Sets the material used by this mesh instance.
*
* @type {import('./materials/material.js').Material}
*/
set material(material) {
this.clearShaders();
const prevMat = this._material;
// Remove the material's reference to this mesh instance
if (prevMat) {
prevMat.removeMeshInstanceRef(this);
}
this._material = material;
if (material) {
// Record that the material is referenced by this mesh instance
material.addMeshInstanceRef(this);
// update transparent flag based on material
this.transparent = material.transparent;
this.updateKey();
}
}
/**
* Gets the material used by this mesh instance.
*
* @type {import('./materials/material.js').Material}
*/
get material() {
return this._material;
}
set layer(layer) {
this._layer = layer;
this.updateKey();
}
get layer() {
return this._layer;
}
_updateShaderDefs(shaderDefs) {
if (shaderDefs !== this._shaderDefs) {
this._shaderDefs = shaderDefs;
this.clearShaders();
}
}
/**
* Sets the callback to calculate sort distance. In some circumstances mesh instances are
* sorted by a distance calculation to determine their rendering order. Set this callback to
* override the default distance calculation, which gives the dot product of the camera forward
* vector and the vector between the camera position and the center of the mesh instance's
* axis-aligned bounding box. This option can be particularly useful for rendering transparent
* meshes in a better order than the default.
*
* @type {CalculateSortDistanceCallback|null}
*/
set calculateSortDistance(calculateSortDistance) {
this._calculateSortDistance = calculateSortDistance;
}
/**
* Gets the callback to calculate sort distance.
*
* @type {CalculateSortDistanceCallback|null}
*/
get calculateSortDistance() {
return this._calculateSortDistance;
}
set receiveShadow(val) {
if (this._receiveShadow !== val) {
this._receiveShadow = val;
this._updateShaderDefs(val ? this._shaderDefs & ~SHADERDEF_NOSHADOW : this._shaderDefs | SHADERDEF_NOSHADOW);
}
}
get receiveShadow() {
return this._receiveShadow;
}
/**
* Sets the skin instance managing skinning of this mesh instance. Set to null if skinning is
* not used.
*
* @type {import('./skin-instance.js').SkinInstance|null}
*/
set skinInstance(val) {
this._skinInstance = val;
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SKIN : this._shaderDefs & ~SHADERDEF_SKIN);
this._setupSkinUpdate();
}
/**
* Gets the skin instance managing skinning of this mesh instance.
*
* @type {import('./skin-instance.js').SkinInstance|null}
*/
get skinInstance() {
return this._skinInstance;
}
/**
* Sets the morph instance managing morphing of this mesh instance. Set to null if morphing is
* not used.
*
* @type {import('./morph-instance.js').MorphInstance|null}
*/
set morphInstance(val) {
var _this$_morphInstance;
// release existing
(_this$_morphInstance = this._morphInstance) == null || _this$_morphInstance.destroy();
// assign new
this._morphInstance = val;
let shaderDefs = this._shaderDefs;
shaderDefs = val && val.morph.useTextureMorph ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED : shaderDefs & ~SHADERDEF_MORPH_TEXTURE_BASED;
shaderDefs = val && val.morph.morphPositions ? shaderDefs | SHADERDEF_MORPH_POSITION : shaderDefs & ~SHADERDEF_MORPH_POSITION;
shaderDefs = val && val.morph.morphNormals ? shaderDefs | SHADERDEF_MORPH_NORMAL : shaderDefs & ~SHADERDEF_MORPH_NORMAL;
this._updateShaderDefs(shaderDefs);
}
/**
* Gets the morph instance managing morphing of this mesh instance.
*
* @type {import('./morph-instance.js').MorphInstance|null}
*/
get morphInstance() {
return this._morphInstance;
}
set screenSpace(val) {
if (this._screenSpace !== val) {
this._screenSpace = val;
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_SCREENSPACE : this._shaderDefs & ~SHADERDEF_SCREENSPACE);
}
}
get screenSpace() {
return this._screenSpace;
}
set key(val) {
this._key[SORTKEY_FORWARD] = val;
}
get key() {
return this._key[SORTKEY_FORWARD];
}
/**
* Sets the mask controlling which {@link LightComponent}s light this mesh instance, which
* {@link CameraComponent} sees it and in which {@link Layer} it is rendered. Defaults to 1.
*
* @type {number}
*/
set mask(val) {
const toggles = this._shaderDefs & 0x0000FFFF;
this._updateShaderDefs(toggles | val << 16);
}
/**
* Gets the mask controlling which {@link LightComponent}s light this mesh instance, which
* {@link CameraComponent} sees it and in which {@link Layer} it is rendered.
*
* @type {number}
*/
get mask() {
return this._shaderDefs >> 16;
}
/**
* Sets the number of instances when using hardware instancing to render the mesh.
*
* @type {number}
*/
set instancingCount(value) {
if (this.instancingData) {
this.instancingData.count = value;
}
}
/**
* Gets the number of instances when using hardware instancing to render the mesh.
*
* @type {number}
*/
get instancingCount() {
return this.instancingData ? this.instancingData.count : 0;
}
destroy() {
var _this$_skinInstance, _this$morphInstance;
const mesh = this.mesh;
if (mesh) {
// this decreases ref count on the mesh
this.mesh = null;
// destroy mesh
if (mesh.refCount < 1) {
mesh.destroy();
}
}
// release ref counted lightmaps
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
(_this$_skinInstance = this._skinInstance) == null || _this$_skinInstance.destroy();
this._skinInstance = null;
(_this$morphInstance = this.morphInstance) == null || _this$morphInstance.destroy();
this.morphInstance = null;
this.clearShaders();
// make sure material clears references to this meshInstance
this.material = null;
}
// shader uniform names for lightmaps
// generates wireframes for an array of mesh instances
static _prepareRenderStyleForArray(meshInstances, renderStyle) {
if (meshInstances) {
for (let i = 0; i < meshInstances.length; i++) {
// switch mesh instance to the requested style
meshInstances[i]._renderStyle = renderStyle;
// process all unique meshes
const mesh = meshInstances[i].mesh;
if (!_meshSet.has(mesh)) {
_meshSet.add(mesh);
mesh.prepareRenderState(renderStyle);
}
}
_meshSet.clear();
}
}
// test if meshInstance is visible by camera. It requires the frustum of the camera to be up to date, which forward-renderer
// takes care of. This function should not be called elsewhere.
_isVisible(camera) {
if (this.visible) {
// custom visibility method of MeshInstance
if (this.isVisibleFunc) {
return this.isVisibleFunc(camera);
}
_tempSphere.center = this.aabb.center; // this line evaluates aabb
_tempSphere.radius = this._aabb.halfExtents.length();
return camera.frustum.containsSphere(_tempSphere);
}
return false;
}
updateKey() {
// render alphatest/atoc after opaque
const material = this.material;
const blendType = material.alphaToCoverage || material.alphaTest ? BLEND_NORMAL : material.blendType;
// Key definition:
// Bit
// 31 : sign bit (leave)
// 27 - 30 : layer
// 26 : translucency type (opaque/transparent)
// 25 : unused
// 0 - 24 : Material ID (if opaque) or 0 (if transparent - will be depth)
this._key[SORTKEY_FORWARD] = (this.layer & 0x0f) << 27 | (blendType === BLEND_NONE ? 1 : 0) << 26 | (material.id & 0x1ffffff) << 0;
}
/**
* Sets up {@link MeshInstance} to be rendered using Hardware Instancing.
*
* @param {import('../platform/graphics/vertex-buffer.js').VertexBuffer|null} vertexBuffer -
* Vertex buffer to hold per-instance vertex data (usually world matrices). Pass null to turn
* off hardware instancing.
* @param {boolean} cull - Whether to perform frustum culling on this instance. If true, the whole
* instance will be culled by the camera frustum. This often involves setting
* {@link RenderComponent#customAabb} containing all instances. Defaults to false, which means
* the whole instance is always rendered.
*/
setInstancing(vertexBuffer, cull = false) {
if (vertexBuffer) {
this.instancingData = new InstancingData(vertexBuffer.numVertices);
this.instancingData.vertexBuffer = vertexBuffer;
// mark vertex buffer as instancing data
vertexBuffer.format.instancing = true;
// set up culling
this.cull = cull;
} else {
this.instancingData = null;
this.cull = true;
}
this._updateShaderDefs(vertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & ~SHADERDEF_INSTANCING);
}
ensureMaterial(device) {
if (!this.material) {
Debug.warn(`Mesh attached to entity '${this.node.name}' does not have a material, using a default one.`);
this.material = getDefaultMaterial(device);
}
}
// Parameter management
clearParameters() {
this.parameters = {};
}
getParameters() {
return this.parameters;
}
/**
* Retrieves the specified shader parameter from a mesh instance.
*
* @param {string} name - The name of the parameter to query.
* @returns {object} The named parameter.
*/
getParameter(name) {
return this.parameters[name];
}
/**
* Sets a shader parameter on a mesh instance. Note that this parameter will take precedence
* over parameter of the same name if set on Material this mesh instance uses for rendering.
*
* @param {string} name - The name of the parameter to set.
* @param {number|number[]|import('../platform/graphics/texture.js').Texture|Float32Array} data - The
* value for the specified parameter.
* @param {number} [passFlags] - Mask describing which passes the material should be included
* in.
*/
setParameter(name, data, passFlags = -262141) {
// note on -262141: All bits set except 2 - 19 range
if (data === undefined && typeof name === 'object') {
const uniformObject = name;
if (uniformObject.length) {
for (let i = 0; i < uniformObject.length; i++) {
this.setParameter(uniformObject[i]);
}
return;
}
name = uniformObject.name;
data = uniformObject.value;
}
const param = this.parameters[name];
if (param) {
param.data = data;
param.passFlags = passFlags;
} else {
this.parameters[name] = {
scopeId: null,
data: data,
passFlags: passFlags
};
}
}
// a wrapper over settings parameter specifically for realtime baked lightmaps. This handles reference counting of lightmaps
// and releases them when no longer referenced
setRealtimeLightmap(name, texture) {
// no change
const old = this.getParameter(name);
if (old === texture) {
return;
}
// remove old
if (old) {
LightmapCache.decRef(old.data);
}
// assign new
if (texture) {
LightmapCache.incRef(texture);
this.setParameter(name, texture);
} else {
this.deleteParameter(name);
}
}
/**
* Deletes a shader parameter on a mesh instance.
*
* @param {string} name - The name of the parameter to delete.
*/
deleteParameter(name) {
if (this.parameters[name]) {
delete this.parameters[name];
}
}
// used to apply parameters from this mesh instance into scope of uniforms, called internally by forward-renderer
setParameters(device, passFlag) {
const parameters = this.parameters;
for (const paramName in parameters) {
const parameter = parameters[paramName];
if (parameter.passFlags & passFlag) {
if (!parameter.scopeId) {
parameter.scopeId = device.scope.resolve(paramName);
}
parameter.scopeId.setValue(parameter.data);
}
}
}
setLightmapped(value) {
if (value) {
this.mask = (this.mask | MASK_AFFECT_LIGHTMAPPED) & ~(MASK_AFFECT_DYNAMIC | MASK_BAKE);
} else {
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[0], null);
this.setRealtimeLightmap(MeshInstance.lightmapParamNames[1], null);
this._shaderDefs &= ~(SHADERDEF_LM | SHADERDEF_DIRLM | SHADERDEF_LMAMBIENT);
this.mask = (this.mask | MASK_AFFECT_DYNAMIC) & ~(MASK_AFFECT_LIGHTMAPPED | MASK_BAKE);
}
}
setCustomAabb(aabb) {
if (aabb) {
// store the override aabb
if (this._customAabb) {
this._customAabb.copy(aabb);
} else {
this._customAabb = aabb.clone();
}
} else {
// no override, force refresh the actual one
this._customAabb = null;
this._aabbVer = -1;
}
this._setupSkinUpdate();
}
_setupSkinUpdate() {
// set if bones need to be updated before culling
if (this._skinInstance) {
this._skinInstance._updateBeforeCull = !this._customAabb;
}
}
}
MeshInstance.lightmapParamNames = ['texture_lightMap', 'texture_dirLightMap'];
export { MeshInstance };