playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
1,084 lines (1,083 loc) • 36.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
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 { VertexBuffer } from "../platform/graphics/vertex-buffer.js";
import { DrawCommands } from "../platform/graphics/draw-commands.js";
import { indexFormatByteSize } from "../platform/graphics/constants.js";
import {
LAYER_WORLD,
MASK_AFFECT_DYNAMIC,
MASK_BAKE,
MASK_AFFECT_LIGHTMAPPED,
RENDERSTYLE_SOLID,
SHADERDEF_UV0,
SHADERDEF_UV1,
SHADERDEF_VCOLOR,
SHADERDEF_TANGENTS,
SHADERDEF_NOSHADOW,
SHADERDEF_SKIN,
SHADERDEF_SCREENSPACE,
SHADERDEF_MORPH_POSITION,
SHADERDEF_MORPH_NORMAL,
SHADERDEF_BATCH,
SHADERDEF_LM,
SHADERDEF_DIRLM,
SHADERDEF_LMAMBIENT,
SHADERDEF_INSTANCING,
SHADERDEF_MORPH_TEXTURE_BASED_INT,
SHADOW_CASCADE_ALL
} 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";
import { hash32Fnv1a } from "../core/hash.js";
import { array } from "../core/array-utils.js";
import { PickerId } from "./picker-id.js";
const _tmpAabb = new BoundingBox();
const _tempBoneAabb = new BoundingBox();
const _tempSphere = new BoundingSphere();
const _meshSet = /* @__PURE__ */ new Set();
const lookupHashes = new Uint32Array(4);
class InstancingData {
/**
* @param {number} numObjects - The number of objects instanced.
*/
constructor(numObjects) {
/** @type {VertexBuffer|null} */
__publicField(this, "vertexBuffer", null);
/**
* True if the vertex buffer is destroyed when the mesh instance is destroyed.
*/
__publicField(this, "_destroyVertexBuffer", false);
this.count = numObjects;
}
destroy() {
if (this._destroyVertexBuffer) {
this.vertexBuffer?.destroy();
}
this.vertexBuffer = null;
}
}
class ShaderInstance {
constructor() {
/**
* A shader.
*
* @type {Shader|undefined}
*/
__publicField(this, "shader");
/**
* A bind group storing mesh textures / samplers for the shader. but not the uniform buffer.
*
* @type {BindGroup|null}
*/
__publicField(this, "bindGroup", null);
/**
* A uniform buffer storing mesh uniforms for the shader.
*
* @type {UniformBuffer|null}
*/
__publicField(this, "uniformBuffer", null);
/**
* The full array of hashes used to lookup the pipeline, used in case of hash collision.
*
* @type {Uint32Array}
*/
__publicField(this, "hashes");
}
/**
* Returns the mesh bind group for the shader.
*
* @param {GraphicsDevice} device - The graphics device.
* @returns {BindGroup} - The mesh bind group.
*/
getBindGroup(device) {
if (!this.bindGroup) {
const shader = this.shader;
Debug.assert(shader);
const bindGroupFormat = shader.meshBindGroupFormat;
Debug.assert(bindGroupFormat);
this.bindGroup = new BindGroup(device, bindGroupFormat);
DebugHelper.setName(this.bindGroup, `MeshBindGroup_${this.bindGroup.id}`);
}
return this.bindGroup;
}
/**
* Returns the uniform buffer for the shader.
*
* @param {GraphicsDevice} device - The graphics device.
* @returns {UniformBuffer} - The uniform buffer.
*/
getUniformBuffer(device) {
if (!this.uniformBuffer) {
const shader = this.shader;
Debug.assert(shader);
const ubFormat = shader.meshUniformBufferFormat;
Debug.assert(ubFormat);
this.uniformBuffer = new UniformBuffer(device, ubFormat, false);
}
return this.uniformBuffer;
}
destroy() {
this.bindGroup?.destroy();
this.bindGroup = null;
this.uniformBuffer?.destroy();
this.uniformBuffer = null;
}
}
const _MeshInstance = class _MeshInstance {
/**
* Create a new MeshInstance instance.
*
* @param {Mesh} mesh - The graphics mesh to instance.
* @param {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 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.
*/
__publicField(this, "castShadow", false);
/**
* Specifies a bitmask that controls which shadow cascades a mesh instance contributes
* to when rendered with a {@link LIGHTTYPE_DIRECTIONAL} light source.
* This setting is only effective if the {@link castShadow} property is enabled.
* Defaults to {@link SHADOW_CASCADE_ALL}, which means the mesh casts shadows into all available cascades.
*
* @type {number}
*/
__publicField(this, "shadowCascadeMask", SHADOW_CASCADE_ALL);
/**
* Controls whether the mesh instance can be culled by frustum culling (see
* {@link CameraComponent#frustumCulling}). Defaults to true.
*/
__publicField(this, "cull", true);
/**
* Determines the 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}.
*/
__publicField(this, "drawOrder", 0);
/** @ignore */
__publicField(this, "_drawBucket", 127);
/**
* The graph node defining the transform for this instance.
*
* @type {GraphNode}
*/
__publicField(this, "node");
/**
* 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.
*/
__publicField(this, "visible", true);
/**
* Read this value in the {@link Scene.EVENT_POSTCULL} event to determine if the object is
* actually going to be rendered.
*/
__publicField(this, "visibleThisFrame", false);
/**
* Negative scale batching support.
*
* @ignore
*/
__publicField(this, "flipFacesFactor", 1);
/**
* @type {GSplatInstance|null}
* @ignore
*/
__publicField(this, "gsplatInstance", null);
/** @ignore */
__publicField(this, "id", PickerId.get());
/**
* Custom function used to customize culling (e.g. for 2D UI elements).
*
* @type {Function|null}
* @ignore
*/
__publicField(this, "isVisibleFunc", null);
/**
* @type {InstancingData|null}
* @ignore
*/
__publicField(this, "instancingData", null);
/**
* @type {DrawCommands|null}
* @ignore
*/
__publicField(this, "indirectData", null);
/**
* Map of camera to their corresponding indirect draw data. Lazily allocated.
*
* @type {Map<Camera|null, DrawCommands>|null}
* @ignore
*/
__publicField(this, "drawCommands", null);
/**
* Stores mesh metadata used for indirect rendering. Lazily allocated on first access
* via getIndirectMetaData().
*
* @type {Int32Array|null}
* @ignore
*/
__publicField(this, "meshMetaData", null);
/**
* @type {Record<string, {scopeId: ScopeId|null, data: any, passFlags: number}>}
* @ignore
*/
__publicField(this, "parameters", {});
/**
* True if the mesh instance is pickable by the {@link Picker}. Defaults to true.
*
* @ignore
*/
__publicField(this, "pick", true);
/**
* The stencil parameters for front faces or null if no stencil is enabled.
*
* @type {StencilParameters|null}
* @ignore
*/
__publicField(this, "stencilFront", null);
/**
* The stencil parameters for back faces or null if no stencil is enabled.
*
* @type {StencilParameters|null}
* @ignore
*/
__publicField(this, "stencilBack", null);
/**
* True if the material of the mesh instance is transparent. Optimization to avoid accessing
* the material. Updated by the material instance itself.
*
* @ignore
*/
__publicField(this, "transparent", false);
/** @private */
__publicField(this, "_aabb", new BoundingBox());
/** @private */
__publicField(this, "_aabbVer", -1);
/** @private */
__publicField(this, "_aabbMeshVer", -1);
/**
* @type {BoundingBox|null}
* @private
*/
__publicField(this, "_customAabb", null);
/** @private */
__publicField(this, "_updateAabb", true);
/** @private */
__publicField(this, "_updateAabbFunc", null);
/**
* The internal sorting key used by the shadow renderer.
*
* @ignore
*/
__publicField(this, "_sortKeyShadow", 0);
/**
* The internal sorting key used by the forward renderer, in case SORTMODE_MATERIALMESH sorting
* is used.
*
* @private
*/
__publicField(this, "_sortKeyForward", 0);
/**
* The internal sorting key used by the forward renderer, in case SORTMODE_BACK2FRONT or
* SORTMODE_FRONT2BACK sorting is used.
*
* @ignore
*/
__publicField(this, "_sortKeyDynamic", 0);
/** @private */
__publicField(this, "_layer", LAYER_WORLD);
/**
* @type {Material|null}
* @private
*/
__publicField(this, "_material", null);
/**
* @type {SkinInstance|null}
* @private
*/
__publicField(this, "_skinInstance", null);
/**
* @type {MorphInstance|null}
* @private
*/
__publicField(this, "_morphInstance", null);
/** @private */
__publicField(this, "_receiveShadow", true);
/** @private */
__publicField(this, "_renderStyle", RENDERSTYLE_SOLID);
/** @private */
__publicField(this, "_screenSpace", false);
/**
* The cache of shaders, indexed by a hash value.
*
* @type {Map<number, ShaderInstance>}
* @private
*/
__publicField(this, "_shaderCache", /* @__PURE__ */ new Map());
/**
* 2 byte toggles, 2 bytes light mask; Default value is no toggles and mask = pc.MASK_AFFECT_DYNAMIC
*
* @private
*/
__publicField(this, "_shaderDefs", MASK_AFFECT_DYNAMIC << 16);
/**
* @type {CalculateSortDistanceCallback|null}
* @private
*/
__publicField(this, "_calculateSortDistance", null);
Debug.assert(!(mesh instanceof GraphNode), "Incorrect parameters for MeshInstance's constructor. Use new MeshInstance(mesh, material, node)");
this.node = node;
this._mesh = mesh;
mesh.incRefCount();
this.material = material;
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;
}
this.updateKey();
}
/**
* Sets the draw bucket for mesh instances. The draw bucket, an integer from 0 to 255 (default
* 127), serves as the primary sort key for mesh rendering. Meshes are sorted by draw bucket,
* then by sort mode. This setting is only effective when mesh instances are added to a
* {@link Layer} with its {@link Layer#opaqueSortMode} or {@link Layer#transparentSortMode}
* (depending on the material) set to {@link SORTMODE_BACK2FRONT}, {@link SORTMODE_FRONT2BACK},
* or {@link SORTMODE_MATERIALMESH}.
*
* Note: When {@link SORTMODE_BACK2FRONT} is used, a descending sort order is used; otherwise,
* an ascending sort order is used.
*
* @type {number}
*/
set drawBucket(bucket) {
this._drawBucket = Math.floor(bucket) & 255;
this.updateKey();
}
/**
* Gets the draw bucket for mesh instance.
*
* @type {number}
*/
get drawBucket() {
return this._drawBucket;
}
/**
* 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 {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 {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() {
if (!this._updateAabb) {
return this._aabb;
}
if (this._updateAabbFunc) {
return this._updateAabbFunc(this._aabb);
}
let localAabb = this._customAabb;
let toWorldSpace = !!localAabb;
if (!localAabb) {
localAabb = _tmpAabb;
if (this.skinInstance) {
if (!this.mesh.boneAabb) {
const morphTargets = this._morphInstance ? this._morphInstance.morph._targets : null;
this.mesh._initBoneAabbs(morphTargets);
}
const boneUsed = this.mesh.boneUsed;
let first = true;
for (let i = 0; i < this.mesh.boneAabb.length; i++) {
if (boneUsed[i]) {
_tempBoneAabb.setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
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) {
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);
}
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;
}
}
if (toWorldSpace) {
this._aabb.setFromTransformedAabb(localAabb, this.node.getWorldTransform());
}
return this._aabb;
}
/**
* Clear the internal shader cache.
*
* @ignore
*/
clearShaders() {
this._shaderCache.forEach((shaderInstance) => {
shaderInstance.destroy();
});
this._shaderCache.clear();
}
/**
* 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 {Scene} scene - The scene.
* @param {CameraShaderParams} cameraShaderParams - The camera shader parameters.
* @param {UniformBufferFormat} [viewUniformFormat] - The format of the view uniform buffer.
* @param {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, cameraShaderParams, viewUniformFormat, viewBindGroupFormat, sortedLights) {
const shaderDefs = this._shaderDefs;
lookupHashes[0] = shaderPass;
lookupHashes[1] = lightHash;
lookupHashes[2] = shaderDefs;
lookupHashes[3] = cameraShaderParams.hash;
const hash = hash32Fnv1a(lookupHashes);
let shaderInstance = this._shaderCache.get(hash);
if (!shaderInstance) {
const mat = this._material;
shaderInstance = new ShaderInstance();
shaderInstance.shader = mat.variants.get(hash);
shaderInstance.hashes = new Uint32Array(lookupHashes);
if (!shaderInstance.shader) {
DebugGraphics.pushGpuMarker(this.mesh.device, `Node: ${this.node.name}`);
const shader = mat.getShaderVariant({
device: this.mesh.device,
scene,
objDefs: shaderDefs,
cameraShaderParams,
pass: shaderPass,
sortedLights,
viewUniformFormat,
viewBindGroupFormat,
vertexFormat: this.mesh.vertexBuffer?.format
});
DebugGraphics.popGpuMarker(this.mesh.device);
mat.variants.set(hash, shader);
shaderInstance.shader = shader;
}
this._shaderCache.set(hash, shaderInstance);
}
Debug.call(() => {
if (!array.equals(shaderInstance.hashes, lookupHashes)) {
Debug.errorOnce("Hash collision in the shader cache for mesh instance. This is very unlikely but still possible. Please report this issue.");
}
});
return shaderInstance;
}
/**
* Sets the material used by this mesh instance.
*
* @type {Material}
*/
set material(material) {
this.clearShaders();
const prevMat = this._material;
if (prevMat) {
prevMat.removeMeshInstanceRef(this);
}
this._material = material;
if (material) {
material.addMeshInstanceRef(this);
this.transparent = material.transparent;
this.updateKey();
}
}
/**
* Gets the material used by this mesh instance.
*
* @type {Material}
*/
get material() {
return this._material;
}
/**
* @param {number} shaderDefs - The shader definitions to set.
* @private
*/
_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;
}
set batching(val) {
this._updateShaderDefs(val ? this._shaderDefs | SHADERDEF_BATCH : this._shaderDefs & ~SHADERDEF_BATCH);
}
get batching() {
return (this._shaderDefs & SHADERDEF_BATCH) !== 0;
}
/**
* Sets the skin instance managing skinning of this mesh instance. Set to null if skinning is
* not used.
*
* @type {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 {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 {MorphInstance|null}
*/
set morphInstance(val) {
this._morphInstance?.destroy();
this._morphInstance = val;
let shaderDefs = this._shaderDefs;
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;
shaderDefs = val && val.morph.intRenderFormat ? shaderDefs | SHADERDEF_MORPH_TEXTURE_BASED_INT : shaderDefs & ~SHADERDEF_MORPH_TEXTURE_BASED_INT;
this._updateShaderDefs(shaderDefs);
}
/**
* Gets the morph instance managing morphing of this mesh instance.
*
* @type {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._sortKeyForward = val;
}
get key() {
return this._sortKeyForward;
}
/**
* 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 & 65535;
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() {
const mesh = this.mesh;
if (mesh) {
this.mesh = null;
if (mesh.refCount < 1) {
mesh.destroy();
}
}
this.setRealtimeLightmap(_MeshInstance.lightmapParamNames[0], null);
this.setRealtimeLightmap(_MeshInstance.lightmapParamNames[1], null);
this._skinInstance?.destroy();
this._skinInstance = null;
this.morphInstance?.destroy();
this.morphInstance = null;
this.clearShaders();
this.material = null;
this.instancingData?.destroy();
this.destroyDrawCommands();
}
destroyDrawCommands() {
if (this.drawCommands) {
for (const cmd of this.drawCommands.values()) {
cmd?.destroy();
}
this.drawCommands = null;
}
}
/**
* Sets the render style for an array of mesh instances.
*
* @param {MeshInstance[]} meshInstances - The mesh instances to set the render style for.
* @param {number} renderStyle - The render style to set.
* @ignore
*/
static _prepareRenderStyleForArray(meshInstances, renderStyle) {
if (meshInstances) {
for (let i = 0; i < meshInstances.length; i++) {
meshInstances[i]._renderStyle = renderStyle;
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.
*
* @param {Camera} camera - The camera to test visibility against.
* @returns {boolean} - True if the mesh instance is visible by the camera, false otherwise.
* @ignore
*/
_isVisible(camera) {
if (this.visible) {
if (this.isVisibleFunc) {
return this.isVisibleFunc(camera);
}
_tempSphere.center = this.aabb.center;
_tempSphere.radius = this._aabb.halfExtents.length();
return camera.frustum.containsSphere(_tempSphere) > 0;
}
return false;
}
updateKey() {
const { material } = this;
this._sortKeyForward = this._drawBucket << 23 | (material.alphaToCoverage || material.alphaTest ? 4194304 : 0) | material.id & 4194303;
}
/**
* Sets up {@link MeshInstance} to be rendered using Hardware Instancing.
* Note that {@link instancingCount} is automatically set to the number of vertices of the
* vertex buffer when it is provided.
*
* @param {VertexBuffer|true|null} vertexBuffer - Vertex buffer to hold per-instance vertex data
* (usually world matrices). Pass `true` to enable attributeless instancing where the instance
* index is derived from `gl_InstanceID` / `instance_index` builtins rather than a vertex
* buffer attribute — the caller must set {@link instancingCount} manually. 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) {
if (vertexBuffer === true) {
this.instancingData = new InstancingData(0);
} else {
this.instancingData = new InstancingData(vertexBuffer.numVertices);
this.instancingData.vertexBuffer = vertexBuffer;
vertexBuffer.format.instancing = true;
}
this.cull = cull;
} else {
this.instancingData = null;
this.cull = true;
}
this._updateShaderDefs(vertexBuffer instanceof VertexBuffer ? this._shaderDefs | SHADERDEF_INSTANCING : this._shaderDefs & ~SHADERDEF_INSTANCING);
}
/**
* Sets the {@link MeshInstance} to be rendered using indirect rendering, where the GPU,
* typically using a Compute shader, stores draw call parameters in a buffer.
* Note that this is only supported on WebGPU, and ignored on other platforms.
*
* @param {CameraComponent|null} camera - Camera component to set indirect data for, or
* null if the indirect slot should be used for all cameras.
* @param {number} slot - Slot in the buffer to set the draw call parameters. Allocate a slot
* in the buffer by calling {@link GraphicsDevice#getIndirectDrawSlot}. Pass -1 to disable
* indirect rendering for the specified camera (or the shared entry when camera is null).
* @param {number} [count] - Optional number of consecutive slots to use. Defaults to 1.
*/
setIndirect(camera, slot, count = 1) {
const key = camera?.camera ?? null;
if (slot === -1) {
this._deleteDrawCommandsKey(key);
} else {
this.drawCommands ?? (this.drawCommands = /* @__PURE__ */ new Map());
const cmd = this.drawCommands.get(key) ?? new DrawCommands(this.mesh.device);
cmd.slotIndex = slot;
cmd.update(count);
this.drawCommands.set(key, cmd);
const device = this.mesh.device;
device.mapsToClear.add(this.drawCommands);
}
}
/**
* Sets the {@link MeshInstance} to be rendered using multi-draw, where multiple sub-draws are
* executed with a single draw call.
*
* Note: Each call to this method invalidates any previously stored draw command data for the
* specified camera.
*
* @param {CameraComponent|null} camera - Camera component to bind commands to, or null to share
* across all cameras.
* @param {number} [maxCount] - Maximum number of sub-draws to allocate. Defaults to 1. Pass 0
* to disable multi-draw for the specified camera (or the shared entry when camera is null).
* @returns {DrawCommands|undefined} The commands container to populate with sub-draw commands.
*/
setMultiDraw(camera, maxCount = 1) {
const key = camera?.camera ?? null;
let cmd;
if (maxCount === 0) {
this._deleteDrawCommandsKey(key);
} else {
this.drawCommands ?? (this.drawCommands = /* @__PURE__ */ new Map());
cmd = this.drawCommands.get(key);
if (!cmd) {
const indexBuffer = this.mesh.indexBuffer?.[0];
const indexFormat = indexBuffer?.format;
const indexSizeBytes = indexFormat !== void 0 ? indexFormatByteSize[indexFormat] : 0;
cmd = new DrawCommands(this.mesh.device, indexSizeBytes);
this.drawCommands.set(key, cmd);
}
cmd.allocate(maxCount);
}
return cmd;
}
_deleteDrawCommandsKey(key) {
const cmds = this.drawCommands;
if (cmds) {
const cmd = cmds.get(key);
cmd?.destroy();
cmds.delete(key);
if (cmds.size === 0) {
this.destroyDrawCommands();
}
}
}
/**
* Retrieves the draw commands for a specific camera, or the default commands when none are
* bound to that camera.
*
* @param {Camera} camera - The camera to retrieve commands for.
* @returns {DrawCommands|undefined} - The draw commands, or undefined.
* @ignore
*/
getDrawCommands(camera) {
const cmds = this.drawCommands;
if (!cmds) return void 0;
return cmds.get(camera) ?? cmds.get(null);
}
/**
* Retrieves the mesh metadata needed for indirect rendering.
*
* @returns {Int32Array} - A typed array with 4 elements representing the mesh metadata, which
* is typically needed when generating indirect draw call parameters using Compute shader. These
* can be provided to the Compute shader using vec4i uniform. The values are based on
* {@link Mesh#primitive}, stored in this order: [count, base, baseVertex, 0]. The last value is
* always zero and is reserved for future use.
*/
getIndirectMetaData() {
const prim = this.mesh?.primitive[this.renderStyle];
const data = this.meshMetaData ?? (this.meshMetaData = new Int32Array(4));
data[0] = prim.count;
data[1] = prim.base;
data[2] = prim.baseVertex;
return data;
}
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|undefined} The named parameter, or `undefined` if no parameter with that
* name is set on this mesh instance.
*/
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[]|Texture|Float32Array} data - The value for the specified parameter.
* @param {number} [passFlags] - Mask describing which passes the material should be included
* in. Defaults to 0xFFFFFFFF (all passes).
*/
setParameter(name, data, passFlags = 4294967295) {
const param = this.parameters[name];
if (param) {
param.data = data;
param.passFlags = passFlags;
} else {
this.parameters[name] = {
scopeId: null,
data,
passFlags
};
}
}
/**
* A wrapper over settings parameter specifically for realtime baked lightmaps. This handles
* reference counting of lightmaps and releases them when no longer referenced.
*
* @param {string} name - The name of the parameter to set.
* @param {Texture|null} texture - The lightmap texture to set.
* @ignore
*/
setRealtimeLightmap(name, texture) {
const old = this.getParameter(name);
if (old === texture) {
return;
}
if (old) {
LightmapCache.decRef(old.data);
}
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.
*
* @param {GraphicsDevice} device - The graphics device.
* @param {number} passFlag - The pass flag for the current render pass.
* @ignore
*/
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);
}
}
}
/**
* @param {boolean} value - True to enable lightmapped rendering, false to disable.
* @ignore
*/
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);
}
}
/**
* @param {BoundingBox|null} aabb - The custom axis-aligned bounding box or null to reset to
* the mesh's bounding box.
* @ignore
*/
setCustomAabb(aabb) {
if (aabb) {
if (this._customAabb) {
this._customAabb.copy(aabb);
} else {
this._customAabb = aabb.clone();
}
} else {
this._customAabb = null;
this._aabbVer = -1;
}
this._setupSkinUpdate();
}
/** @private */
_setupSkinUpdate() {
if (this._skinInstance) {
this._skinInstance._updateBeforeCull = !this._customAabb;
}
}
};
// shader uniform names for lightmaps
__publicField(_MeshInstance, "lightmapParamNames", ["texture_lightMap", "texture_dirLightMap"]);
let MeshInstance = _MeshInstance;
export {
MeshInstance
};