UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

1,175 lines (1,173 loc) 227 kB
import { Observable } from "../Misc/observable.js"; import { Tools, AsyncLoop } from "../Misc/tools.js"; import { DeepCopier } from "../Misc/deepCopier.js"; import { Tags } from "../Misc/tags.js"; import { runCoroutineSync, runCoroutineAsync, createYieldingScheduler } from "../Misc/coroutine.js"; import { Camera } from "../Cameras/camera.js"; import { Quaternion, Matrix, Vector3, Vector2 } from "../Maths/math.vector.js"; import { Color3 } from "../Maths/math.color.js"; import { Node } from "../node.js"; import { VertexBuffer, Buffer } from "../Buffers/buffer.js"; import { VertexData } from "./mesh.vertexData.js"; import { Geometry } from "./geometry.js"; import { AbstractMesh } from "./abstractMesh.js"; import { SubMesh } from "./subMesh.js"; import { Material } from "../Materials/material.js"; import { MultiMaterial } from "../Materials/multiMaterial.js"; import { SceneLoaderFlags } from "../Loading/sceneLoaderFlags.js"; import { SerializationHelper } from "../Misc/decorators.serialization.js"; import { Logger } from "../Misc/logger.js"; import { GetClass, RegisterClass } from "../Misc/typeStore.js"; import { _WarnImport } from "../Misc/devTools.js"; import { SceneComponentConstants } from "../sceneComponent.js"; import { MeshLODLevel } from "./meshLODLevel.js"; /** * @internal **/ export class _CreationDataStorage { } /** * @internal **/ class _InstanceDataStorageRenderPass { constructor() { this.batchCache = new _InstancesBatch(this); this.batchCacheReplacementModeInFrozenMode = new _InstancesBatch(this); this.instancesBufferSize = 32 * 16 * 4; // let's start with a maximum of 32 instances } } /** * @internal **/ class _InstanceDataStorage { constructor() { this.renderPasses = {}; } } /** * @internal **/ export class _InstancesBatch { constructor(parent) { this.parent = parent; this.mustReturn = false; this.visibleInstances = new Array(); this.renderSelf = []; this.hardwareInstancedRendering = []; } } /** * @internal **/ class _ThinInstanceDataStorage { constructor() { this.instancesCount = 0; this.matrixBuffer = null; this.previousMatrixBuffer = null; this.matrixBufferSize = 32 * 16; // let's start with a maximum of 32 thin instances this.matrixData = null; this.boundingVectors = []; this.worldMatrices = null; } } /** * @internal **/ class _InternalMeshDataInfo { constructor() { this._areNormalsFrozen = false; // Will be used by ribbons mainly // Will be used to save a source mesh reference, If any this._source = null; // Will be used to for fast cloned mesh lookup this.meshMap = null; this._preActivateId = -1; // eslint-disable-next-line @typescript-eslint/naming-convention this._LODLevels = new Array(); /** Alternative definition of LOD level, using screen coverage instead of distance */ this._useLODScreenCoverage = false; this._effectiveMaterial = null; this._forcedInstanceCount = 0; this._overrideRenderingFillMode = null; } } const MeshCreationOptions = { source: null, parent: null, doNotCloneChildren: false, clonePhysicsImpostor: true, cloneThinInstances: false, }; /** * Class used to represent renderable models */ export class Mesh extends AbstractMesh { /** * Gets the default side orientation. * @param orientation the orientation to value to attempt to get * @returns the default orientation * @internal */ static _GetDefaultSideOrientation(orientation) { return orientation || Mesh.FRONTSIDE; // works as Mesh.FRONTSIDE is 0 } /** * Determines if the LOD levels are intended to be calculated using screen coverage (surface area ratio) instead of distance. */ get useLODScreenCoverage() { return this._internalMeshDataInfo._useLODScreenCoverage; } set useLODScreenCoverage(value) { this._internalMeshDataInfo._useLODScreenCoverage = value; this._sortLODLevels(); } get computeBonesUsingShaders() { return this._internalAbstractMeshDataInfo._computeBonesUsingShaders; } set computeBonesUsingShaders(value) { if (this._internalAbstractMeshDataInfo._computeBonesUsingShaders === value) { return; } if (value && this._internalMeshDataInfo._sourcePositions) { // switch from software to GPU computation: we need to reset the vertex and normal buffers that have been updated by the software process this.setVerticesData(VertexBuffer.PositionKind, this._internalMeshDataInfo._sourcePositions, true); if (this._internalMeshDataInfo._sourceNormals) { this.setVerticesData(VertexBuffer.NormalKind, this._internalMeshDataInfo._sourceNormals, true); } this._internalMeshDataInfo._sourcePositions = null; this._internalMeshDataInfo._sourceNormals = null; } this._internalAbstractMeshDataInfo._computeBonesUsingShaders = value; this._markSubMeshesAsAttributesDirty(); } /** * An event triggered before rendering the mesh */ get onBeforeRenderObservable() { if (!this._internalMeshDataInfo._onBeforeRenderObservable) { this._internalMeshDataInfo._onBeforeRenderObservable = new Observable(); } return this._internalMeshDataInfo._onBeforeRenderObservable; } /** * An event triggered before binding the mesh */ get onBeforeBindObservable() { if (!this._internalMeshDataInfo._onBeforeBindObservable) { this._internalMeshDataInfo._onBeforeBindObservable = new Observable(); } return this._internalMeshDataInfo._onBeforeBindObservable; } /** * An event triggered after rendering the mesh */ get onAfterRenderObservable() { if (!this._internalMeshDataInfo._onAfterRenderObservable) { this._internalMeshDataInfo._onAfterRenderObservable = new Observable(); } return this._internalMeshDataInfo._onAfterRenderObservable; } /** * An event triggeredbetween rendering pass when using separateCullingPass = true */ get onBetweenPassObservable() { if (!this._internalMeshDataInfo._onBetweenPassObservable) { this._internalMeshDataInfo._onBetweenPassObservable = new Observable(); } return this._internalMeshDataInfo._onBetweenPassObservable; } /** * An event triggered before drawing the mesh */ get onBeforeDrawObservable() { if (!this._internalMeshDataInfo._onBeforeDrawObservable) { this._internalMeshDataInfo._onBeforeDrawObservable = new Observable(); } return this._internalMeshDataInfo._onBeforeDrawObservable; } /** * Sets a callback to call before drawing the mesh. It is recommended to use onBeforeDrawObservable instead */ set onBeforeDraw(callback) { if (this._onBeforeDrawObserver) { this.onBeforeDrawObservable.remove(this._onBeforeDrawObserver); } this._onBeforeDrawObserver = this.onBeforeDrawObservable.add(callback); } get hasInstances() { return this.instances.length > 0; } get hasThinInstances() { return (this.forcedInstanceCount || this._thinInstanceDataStorage.instancesCount || 0) > 0; } /** * Gets or sets the forced number of instances to display. * If 0 (default value), the number of instances is not forced and depends on the draw type * (regular / instance / thin instances mesh) */ get forcedInstanceCount() { return this._internalMeshDataInfo._forcedInstanceCount; } set forcedInstanceCount(count) { this._internalMeshDataInfo._forcedInstanceCount = count; } /** * Use this property to change the original side orientation defined at construction time * Material.sideOrientation will override this value if set * User will still be able to change the material sideOrientation afterwards if they really need it */ get sideOrientation() { return this._internalMeshDataInfo._sideOrientation; } set sideOrientation(value) { this._internalMeshDataInfo._sideOrientation = value; this._internalAbstractMeshDataInfo._sideOrientationHint = (this._scene.useRightHandedSystem && value === 1) || (!this._scene.useRightHandedSystem && value === 0); } /** @internal */ get _effectiveSideOrientation() { return this._internalMeshDataInfo._effectiveSideOrientation; } /** * @deprecated Please use sideOrientation instead. * @see https://doc.babylonjs.com/breaking-changes#7110 */ get overrideMaterialSideOrientation() { return this.sideOrientation; } set overrideMaterialSideOrientation(value) { this.sideOrientation = value; if (this.material) { this.material.sideOrientation = null; } } /** * Use this property to override the Material's fillMode value */ get overrideRenderingFillMode() { return this._internalMeshDataInfo._overrideRenderingFillMode; } set overrideRenderingFillMode(fillMode) { this._internalMeshDataInfo._overrideRenderingFillMode = fillMode; } get material() { return this._internalAbstractMeshDataInfo._material; } set material(value) { if (value && ((this.material && this.material.sideOrientation === null) || this._internalAbstractMeshDataInfo._sideOrientationHint)) { value.sideOrientation = null; } this._setMaterial(value); } /** * Gets the source mesh (the one used to clone this one from) */ get source() { return this._internalMeshDataInfo._source; } /** * Gets the list of clones of this mesh * The scene must have been constructed with useClonedMeshMap=true for this to work! * Note that useClonedMeshMap=true is the default setting */ get cloneMeshMap() { return this._internalMeshDataInfo.meshMap; } /** * Gets or sets a boolean indicating that this mesh does not use index buffer */ get isUnIndexed() { return this._unIndexed; } set isUnIndexed(value) { if (this._unIndexed !== value) { this._unIndexed = value; this._markSubMeshesAsAttributesDirty(); } } /** Gets the array buffer used to store the instanced buffer used for instances' world matrices */ get worldMatrixInstancedBuffer() { const instanceDataStorage = this._instanceDataStorage.renderPasses[this._instanceDataStorage.engine.isWebGPU ? this._instanceDataStorage.engine.currentRenderPassId : 0]; return instanceDataStorage ? instanceDataStorage.instancesData : undefined; } /** Gets the array buffer used to store the instanced buffer used for instances' previous world matrices */ get previousWorldMatrixInstancedBuffer() { const instanceDataStorage = this._instanceDataStorage.renderPasses[this._instanceDataStorage.engine.isWebGPU ? this._instanceDataStorage.engine.currentRenderPassId : 0]; return instanceDataStorage ? instanceDataStorage.instancesPreviousData : undefined; } /** Gets or sets a boolean indicating that the update of the instance buffer of the world matrices is manual */ get manualUpdateOfWorldMatrixInstancedBuffer() { return this._instanceDataStorage.manualUpdate; } set manualUpdateOfWorldMatrixInstancedBuffer(value) { this._instanceDataStorage.manualUpdate = value; } /** Gets or sets a boolean indicating that the update of the instance buffer of the world matrices is manual */ get manualUpdateOfPreviousWorldMatrixInstancedBuffer() { return this._instanceDataStorage.previousManualUpdate; } set manualUpdateOfPreviousWorldMatrixInstancedBuffer(value) { this._instanceDataStorage.previousManualUpdate = value; } /** Gets or sets a boolean indicating that the update of the instance buffer of the world matrices must be performed in all cases (and notably even in frozen mode) */ get forceWorldMatrixInstancedBufferUpdate() { return this._instanceDataStorage.forceMatrixUpdates; } set forceWorldMatrixInstancedBufferUpdate(value) { this._instanceDataStorage.forceMatrixUpdates = value; } _copySource(source, doNotCloneChildren, clonePhysicsImpostor = true, cloneThinInstances = false) { const scene = this.getScene(); // Geometry if (source._geometry) { source._geometry.applyToMesh(this); } // Deep copy DeepCopier.DeepCopy(source, this, [ "name", "material", "skeleton", "instances", "parent", "uniqueId", "source", "metadata", "morphTargetManager", "hasInstances", "worldMatrixInstancedBuffer", "previousWorldMatrixInstancedBuffer", "hasLODLevels", "geometry", "isBlocked", "areNormalsFrozen", "facetNb", "isFacetDataEnabled", "lightSources", "useBones", "isAnInstance", "collider", "edgesRenderer", "forward", "up", "right", "absolutePosition", "absoluteScaling", "absoluteRotationQuaternion", "isWorldMatrixFrozen", "nonUniformScaling", "behaviors", "worldMatrixFromCache", "hasThinInstances", "cloneMeshMap", "hasBoundingInfo", "physicsBody", "physicsImpostor", ], ["_poseMatrix"]); // Source mesh this._internalMeshDataInfo._source = source; if (scene.useClonedMeshMap) { if (!source._internalMeshDataInfo.meshMap) { source._internalMeshDataInfo.meshMap = {}; } source._internalMeshDataInfo.meshMap[this.uniqueId] = this; } // Construction Params // Clone parameters allowing mesh to be updated in case of parametric shapes. this._originalBuilderSideOrientation = source._originalBuilderSideOrientation; this._creationDataStorage = source._creationDataStorage; // Animation ranges if (source._ranges) { const ranges = source._ranges; for (const name in ranges) { if (!Object.prototype.hasOwnProperty.call(ranges, name)) { continue; } if (!ranges[name]) { continue; } this.createAnimationRange(name, ranges[name].from, ranges[name].to); } } // Metadata if (source.metadata && source.metadata.clone) { this.metadata = source.metadata.clone(); } else { this.metadata = source.metadata; } this._internalMetadata = source._internalMetadata; // Tags if (Tags && Tags.HasTags(source)) { Tags.AddTagsTo(this, Tags.GetTags(source, true)); } // Enabled. We shouldn't need to check the source's ancestors, as this mesh // will have the same ones. this.setEnabled(source.isEnabled(false)); // Parent this.parent = source.parent; // Pivot this.setPivotMatrix(source.getPivotMatrix(), this._postMultiplyPivotMatrix); this.id = this.name + "." + source.id; // Material this.material = source.material; if (!doNotCloneChildren) { // Children const directDescendants = source.getDescendants(true); for (let index = 0; index < directDescendants.length; index++) { const child = directDescendants[index]; if (child._isMesh) { MeshCreationOptions.parent = this; MeshCreationOptions.doNotCloneChildren = doNotCloneChildren; MeshCreationOptions.clonePhysicsImpostor = clonePhysicsImpostor; MeshCreationOptions.cloneThinInstances = cloneThinInstances; child.clone(this.name + "." + child.name, MeshCreationOptions); } else if (child.clone) { child.clone(this.name + "." + child.name, this); } } } // Morphs if (source.morphTargetManager) { this.morphTargetManager = source.morphTargetManager; } // Physics clone if (scene.getPhysicsEngine) { const physicsEngine = scene.getPhysicsEngine(); if (clonePhysicsImpostor && physicsEngine) { if (physicsEngine.getPluginVersion() === 1) { const impostor = physicsEngine.getImpostorForPhysicsObject(source); if (impostor) { this.physicsImpostor = impostor.clone(this); } } else if (physicsEngine.getPluginVersion() === 2) { if (source.physicsBody) { source.physicsBody.clone(this); } } } } // Particles for (let index = 0; index < scene.particleSystems.length; index++) { const system = scene.particleSystems[index]; if (system.emitter === source) { system.clone(system.name, this); } } // Skeleton this.skeleton = source.skeleton; // Thin instances if (cloneThinInstances) { if (source._thinInstanceDataStorage.matrixData) { this.thinInstanceSetBuffer("matrix", new Float32Array(source._thinInstanceDataStorage.matrixData), 16, !source._thinInstanceDataStorage.matrixBuffer.isUpdatable()); this._thinInstanceDataStorage.matrixBufferSize = source._thinInstanceDataStorage.matrixBufferSize; this._thinInstanceDataStorage.instancesCount = source._thinInstanceDataStorage.instancesCount; } else { this._thinInstanceDataStorage.matrixBufferSize = source._thinInstanceDataStorage.matrixBufferSize; } if (source._userThinInstanceBuffersStorage) { const userThinInstance = source._userThinInstanceBuffersStorage; for (const kind in userThinInstance.data) { this.thinInstanceSetBuffer(kind, new Float32Array(userThinInstance.data[kind]), userThinInstance.strides[kind], !userThinInstance.vertexBuffers?.[kind]?.isUpdatable()); this._userThinInstanceBuffersStorage.sizes[kind] = userThinInstance.sizes[kind]; } } } this.refreshBoundingInfo(true, true); this.computeWorldMatrix(true); } /** @internal */ constructor(name, scene = null, parentOrOptions = null, source = null, doNotCloneChildren, clonePhysicsImpostor = true) { super(name, scene); // Internal data this._internalMeshDataInfo = new _InternalMeshDataInfo(); // Members /** * Gets the delay loading state of the mesh (when delay loading is turned on) * @see https://doc.babylonjs.com/features/featuresDeepDive/importers/incrementalLoading */ this.delayLoadState = 0; /** * Gets the list of instances created from this mesh * it is not supposed to be modified manually. * Note also that the order of the InstancedMesh wihin the array is not significant and might change. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/copies/instances */ this.instances = []; // Private /** @internal */ this._creationDataStorage = null; /** @internal */ this._geometry = null; /** @internal */ this._thinInstanceDataStorage = new _ThinInstanceDataStorage(); /** @internal */ this._shouldGenerateFlatShading = false; // Use by builder only to know what orientation were the mesh build in. /** @internal */ this._originalBuilderSideOrientation = Mesh.DEFAULTSIDE; /** * Gets or sets a boolean indicating whether to render ignoring the active camera's max z setting. (false by default) * You should not mix meshes that have this property set to true with meshes that have it set to false if they all write * to the depth buffer, because the z-values are not comparable in the two cases and you will get rendering artifacts if you do. * You can set the property to true for meshes that do not write to the depth buffer, or set the same value (either false or true) otherwise. * Note this will reduce performance when set to true. */ this.ignoreCameraMaxZ = false; scene = this.getScene(); this._instanceDataStorage = new _InstanceDataStorage(); this._instanceDataStorage.engine = scene.getEngine(); if (this._scene.useRightHandedSystem) { this.sideOrientation = 0; } else { this.sideOrientation = 1; } this._onBeforeDraw = (isInstance, world, effectiveMaterial) => { if (isInstance && effectiveMaterial) { if (this._uniformBuffer) { this.transferToEffect(world); } else { effectiveMaterial.bindOnlyWorldMatrix(world); } } }; let parent = null; let cloneThinInstances = false; if (parentOrOptions && parentOrOptions._addToSceneRootNodes === undefined) { const options = parentOrOptions; parent = options.parent ?? null; source = options.source ?? null; doNotCloneChildren = options.doNotCloneChildren ?? false; clonePhysicsImpostor = options.clonePhysicsImpostor ?? true; cloneThinInstances = options.cloneThinInstances ?? false; } else { parent = parentOrOptions; } if (source) { this._copySource(source, doNotCloneChildren, clonePhysicsImpostor, cloneThinInstances); } // Parent if (parent !== null) { this.parent = parent; } this._instanceDataStorage.hardwareInstancedRendering = this.getEngine().getCaps().instancedArrays; this._internalMeshDataInfo._onMeshReadyObserverAdded = (observer) => { // only notify once! then unregister the observer observer.unregisterOnNextCall = true; if (this.isReady(true)) { this.onMeshReadyObservable.notifyObservers(this); } else { if (!this._internalMeshDataInfo._checkReadinessObserver) { this._internalMeshDataInfo._checkReadinessObserver = this._scene.onBeforeRenderObservable.add(() => { // check for complete readiness if (this.isReady(true)) { this._scene.onBeforeRenderObservable.remove(this._internalMeshDataInfo._checkReadinessObserver); this._internalMeshDataInfo._checkReadinessObserver = null; this.onMeshReadyObservable.notifyObservers(this); } }); } } }; this.onMeshReadyObservable = new Observable(this._internalMeshDataInfo._onMeshReadyObserverAdded); if (source) { source.onClonedObservable.notifyObservers(this); } } instantiateHierarchy(newParent = null, options, onNewNodeCreated) { const instance = this.getTotalVertices() === 0 || (options && options.doNotInstantiate && (options.doNotInstantiate === true || options.doNotInstantiate(this))) ? this.clone("Clone of " + (this.name || this.id), newParent || this.parent, true) : this.createInstance("instance of " + (this.name || this.id)); instance.parent = newParent || this.parent; instance.position = this.position.clone(); instance.scaling = this.scaling.clone(); if (this.rotationQuaternion) { instance.rotationQuaternion = this.rotationQuaternion.clone(); } else { instance.rotation = this.rotation.clone(); } if (onNewNodeCreated) { onNewNodeCreated(this, instance); } for (const child of this.getChildTransformNodes(true)) { // instancedMesh should have a different sourced mesh if (child.getClassName() === "InstancedMesh" && instance.getClassName() === "Mesh" && child.sourceMesh === this) { child.instantiateHierarchy(instance, { doNotInstantiate: (options && options.doNotInstantiate) || false, newSourcedMesh: instance, }, onNewNodeCreated); } else { child.instantiateHierarchy(instance, options, onNewNodeCreated); } } return instance; } /** * Gets the class name * @returns the string "Mesh". */ getClassName() { return "Mesh"; } /** @internal */ get _isMesh() { return true; } /** * Returns a description of this mesh * @param fullDetails define if full details about this mesh must be used * @returns a descriptive string representing this mesh */ toString(fullDetails) { let ret = super.toString(fullDetails); ret += ", n vertices: " + this.getTotalVertices(); ret += ", parent: " + (this._waitingParentId ? this._waitingParentId : this.parent ? this.parent.name : "NONE"); if (this.animations) { for (let i = 0; i < this.animations.length; i++) { ret += ", animation[0]: " + this.animations[i].toString(fullDetails); } } if (fullDetails) { if (this._geometry) { const ib = this.getIndices(); const vb = this.getVerticesData(VertexBuffer.PositionKind); if (vb && ib) { ret += ", flat shading: " + (vb.length / 3 === ib.length ? "YES" : "NO"); } } else { ret += ", flat shading: UNKNOWN"; } } return ret; } /** @internal */ _unBindEffect() { super._unBindEffect(); for (const instance of this.instances) { instance._unBindEffect(); } } /** * Gets a boolean indicating if this mesh has LOD */ get hasLODLevels() { return this._internalMeshDataInfo._LODLevels.length > 0; } /** * Gets the list of MeshLODLevel associated with the current mesh * @returns an array of MeshLODLevel */ getLODLevels() { return this._internalMeshDataInfo._LODLevels; } _sortLODLevels() { const sortingOrderFactor = this._internalMeshDataInfo._useLODScreenCoverage ? -1 : 1; this._internalMeshDataInfo._LODLevels.sort((a, b) => { if (a.distanceOrScreenCoverage < b.distanceOrScreenCoverage) { return sortingOrderFactor; } if (a.distanceOrScreenCoverage > b.distanceOrScreenCoverage) { return -sortingOrderFactor; } return 0; }); } /** * Add a mesh as LOD level triggered at the given distance. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/LOD * @param distanceOrScreenCoverage Either distance from the center of the object to show this level or the screen coverage if `useScreenCoverage` is set to `true`. * If screen coverage, value is a fraction of the screen's total surface, between 0 and 1. * Example Playground for distance https://playground.babylonjs.com/#QE7KM#197 * Example Playground for screen coverage https://playground.babylonjs.com/#QE7KM#196 * @param mesh The mesh to be added as LOD level (can be null) * @returns This mesh (for chaining) */ addLODLevel(distanceOrScreenCoverage, mesh) { if (mesh && mesh._masterMesh) { Logger.Warn("You cannot use a mesh as LOD level twice"); return this; } const level = new MeshLODLevel(distanceOrScreenCoverage, mesh); this._internalMeshDataInfo._LODLevels.push(level); if (mesh) { mesh._masterMesh = this; } this._sortLODLevels(); return this; } /** * Returns the LOD level mesh at the passed distance or null if not found. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/LOD * @param distance The distance from the center of the object to show this level * @returns a Mesh or `null` */ getLODLevelAtDistance(distance) { const internalDataInfo = this._internalMeshDataInfo; for (let index = 0; index < internalDataInfo._LODLevels.length; index++) { const level = internalDataInfo._LODLevels[index]; if (level.distanceOrScreenCoverage === distance) { return level.mesh; } } return null; } /** * Remove a mesh from the LOD array * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/LOD * @param mesh defines the mesh to be removed * @returns This mesh (for chaining) */ removeLODLevel(mesh) { const internalDataInfo = this._internalMeshDataInfo; for (let index = 0; index < internalDataInfo._LODLevels.length; index++) { if (internalDataInfo._LODLevels[index].mesh === mesh) { internalDataInfo._LODLevels.splice(index, 1); if (mesh) { mesh._masterMesh = null; } } } this._sortLODLevels(); return this; } /** * Returns the registered LOD mesh distant from the parameter `camera` position if any, else returns the current mesh. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/LOD * @param camera defines the camera to use to compute distance * @param boundingSphere defines a custom bounding sphere to use instead of the one from this mesh * @returns This mesh (for chaining) */ getLOD(camera, boundingSphere) { const internalDataInfo = this._internalMeshDataInfo; if (!internalDataInfo._LODLevels || internalDataInfo._LODLevels.length === 0) { return this; } const bSphere = boundingSphere || this.getBoundingInfo().boundingSphere; const distanceToCamera = camera.mode === Camera.ORTHOGRAPHIC_CAMERA ? camera.minZ : bSphere.centerWorld.subtract(camera.globalPosition).length(); let compareValue = distanceToCamera; let compareSign = 1; if (internalDataInfo._useLODScreenCoverage) { const screenArea = camera.screenArea; let meshArea = (bSphere.radiusWorld * camera.minZ) / distanceToCamera; meshArea = meshArea * meshArea * Math.PI; compareValue = meshArea / screenArea; compareSign = -1; } if (compareSign * internalDataInfo._LODLevels[internalDataInfo._LODLevels.length - 1].distanceOrScreenCoverage > compareSign * compareValue) { if (this.onLODLevelSelection) { this.onLODLevelSelection(compareValue, this, this); } return this; } for (let index = 0; index < internalDataInfo._LODLevels.length; index++) { const level = internalDataInfo._LODLevels[index]; if (compareSign * level.distanceOrScreenCoverage < compareSign * compareValue) { if (level.mesh) { if (level.mesh.delayLoadState === 4) { level.mesh._checkDelayState(); return this; } if (level.mesh.delayLoadState === 2) { return this; } level.mesh._preActivate(); level.mesh._updateSubMeshesBoundingInfo(this.worldMatrixFromCache); } if (this.onLODLevelSelection) { this.onLODLevelSelection(compareValue, this, level.mesh); } return level.mesh; } } if (this.onLODLevelSelection) { this.onLODLevelSelection(compareValue, this, this); } return this; } /** * Gets the mesh internal Geometry object */ get geometry() { return this._geometry; } /** * Returns the total number of vertices within the mesh geometry or zero if the mesh has no geometry. * @returns the total number of vertices */ getTotalVertices() { if (this._geometry === null || this._geometry === undefined) { return 0; } return this._geometry.getTotalVertices(); } /** * Returns the content of an associated vertex buffer * @param kind defines which buffer to read from (positions, indices, normals, etc). Possible `kind` values : * - VertexBuffer.PositionKind * - VertexBuffer.UVKind * - VertexBuffer.UV2Kind * - VertexBuffer.UV3Kind * - VertexBuffer.UV4Kind * - VertexBuffer.UV5Kind * - VertexBuffer.UV6Kind * - VertexBuffer.ColorKind * - VertexBuffer.MatricesIndicesKind * - VertexBuffer.MatricesIndicesExtraKind * - VertexBuffer.MatricesWeightsKind * - VertexBuffer.MatricesWeightsExtraKind * @param copyWhenShared defines a boolean indicating that if the mesh geometry is shared among some other meshes, the returned array is a copy of the internal one * @param forceCopy defines a boolean forcing the copy of the buffer no matter what the value of copyWhenShared is * @param bypassInstanceData defines a boolean indicating that the function should not take into account the instance data (applies only if the mesh has instances). Default: false * @returns a FloatArray or null if the mesh has no geometry or no vertex buffer for this kind. */ getVerticesData(kind, copyWhenShared, forceCopy, bypassInstanceData) { if (!this._geometry) { return null; } let data = bypassInstanceData ? undefined : this._userInstancedBuffersStorage?.vertexBuffers[kind]?.getFloatData(this.instances.length + 1, // +1 because the master mesh is not included in the instances array forceCopy || (copyWhenShared && this._geometry.meshes.length !== 1)); if (!data) { data = this._geometry.getVerticesData(kind, copyWhenShared, forceCopy); } return data; } copyVerticesData(kind, vertexData) { if (this._geometry) { this._geometry.copyVerticesData(kind, vertexData); } } /** * Returns the mesh VertexBuffer object from the requested `kind` * @param kind defines which buffer to read from (positions, indices, normals, etc). Possible `kind` values : * - VertexBuffer.PositionKind * - VertexBuffer.NormalKind * - VertexBuffer.UVKind * - VertexBuffer.UV2Kind * - VertexBuffer.UV3Kind * - VertexBuffer.UV4Kind * - VertexBuffer.UV5Kind * - VertexBuffer.UV6Kind * - VertexBuffer.ColorKind * - VertexBuffer.MatricesIndicesKind * - VertexBuffer.MatricesIndicesExtraKind * - VertexBuffer.MatricesWeightsKind * - VertexBuffer.MatricesWeightsExtraKind * @param bypassInstanceData defines a boolean indicating that the function should not take into account the instance data (applies only if the mesh has instances). Default: false * @returns a FloatArray or null if the mesh has no vertex buffer for this kind. */ getVertexBuffer(kind, bypassInstanceData) { if (!this._geometry) { return null; } return (bypassInstanceData ? undefined : this._userInstancedBuffersStorage?.vertexBuffers[kind]) ?? this._geometry.getVertexBuffer(kind); } /** * Tests if a specific vertex buffer is associated with this mesh * @param kind defines which buffer to check (positions, indices, normals, etc). Possible `kind` values : * - VertexBuffer.PositionKind * - VertexBuffer.NormalKind * - VertexBuffer.UVKind * - VertexBuffer.UV2Kind * - VertexBuffer.UV3Kind * - VertexBuffer.UV4Kind * - VertexBuffer.UV5Kind * - VertexBuffer.UV6Kind * - VertexBuffer.ColorKind * - VertexBuffer.MatricesIndicesKind * - VertexBuffer.MatricesIndicesExtraKind * - VertexBuffer.MatricesWeightsKind * - VertexBuffer.MatricesWeightsExtraKind * @param bypassInstanceData defines a boolean indicating that the function should not take into account the instance data (applies only if the mesh has instances). Default: false * @returns a boolean */ isVerticesDataPresent(kind, bypassInstanceData) { if (!this._geometry) { if (this._delayInfo) { return this._delayInfo.indexOf(kind) !== -1; } return false; } return (!bypassInstanceData && this._userInstancedBuffersStorage?.vertexBuffers[kind] !== undefined) || this._geometry.isVerticesDataPresent(kind); } /** * Returns a boolean defining if the vertex data for the requested `kind` is updatable. * @param kind defines which buffer to check (positions, indices, normals, etc). Possible `kind` values : * - VertexBuffer.PositionKind * - VertexBuffer.UVKind * - VertexBuffer.UV2Kind * - VertexBuffer.UV3Kind * - VertexBuffer.UV4Kind * - VertexBuffer.UV5Kind * - VertexBuffer.UV6Kind * - VertexBuffer.ColorKind * - VertexBuffer.MatricesIndicesKind * - VertexBuffer.MatricesIndicesExtraKind * - VertexBuffer.MatricesWeightsKind * - VertexBuffer.MatricesWeightsExtraKind * @param bypassInstanceData defines a boolean indicating that the function should not take into account the instance data (applies only if the mesh has instances). Default: false * @returns a boolean */ isVertexBufferUpdatable(kind, bypassInstanceData) { if (!this._geometry) { if (this._delayInfo) { return this._delayInfo.indexOf(kind) !== -1; } return false; } if (!bypassInstanceData) { const buffer = this._userInstancedBuffersStorage?.vertexBuffers[kind]; if (buffer) { return buffer.isUpdatable(); } } return this._geometry.isVertexBufferUpdatable(kind); } /** * Returns a string which contains the list of existing `kinds` of Vertex Data associated with this mesh. * @param bypassInstanceData defines a boolean indicating that the function should not take into account the instance data (applies only if the mesh has instances). Default: false * @returns an array of strings */ getVerticesDataKinds(bypassInstanceData) { if (!this._geometry) { const result = []; if (this._delayInfo) { for (const kind of this._delayInfo) { result.push(kind); } } return result; } const kinds = this._geometry.getVerticesDataKinds(); if (!bypassInstanceData && this._userInstancedBuffersStorage) { for (const kind in this._userInstancedBuffersStorage.vertexBuffers) { if (kinds.indexOf(kind) === -1) { kinds.push(kind); } } } return kinds; } /** * Returns a positive integer : the total number of indices in this mesh geometry. * @returns the number of indices or zero if the mesh has no geometry. */ getTotalIndices() { if (!this._geometry) { return 0; } return this._geometry.getTotalIndices(); } /** * Returns an array of integers or a typed array (Int32Array, Uint32Array, Uint16Array) populated with the mesh indices. * @param copyWhenShared If true (default false) and and if the mesh geometry is shared among some other meshes, the returned array is a copy of the internal one. * @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it * @returns the indices array or an empty array if the mesh has no geometry */ getIndices(copyWhenShared, forceCopy) { if (!this._geometry) { return []; } return this._geometry.getIndices(copyWhenShared, forceCopy); } get isBlocked() { return this._masterMesh !== null && this._masterMesh !== undefined; } /** * Determine if the current mesh is ready to be rendered * @param completeCheck defines if a complete check (including materials and lights) has to be done (false by default) * @param forceInstanceSupport will check if the mesh will be ready when used with instances (false by default) * @returns true if all associated assets are ready (material, textures, shaders) */ isReady(completeCheck = false, forceInstanceSupport = false) { if (this.delayLoadState === 2) { return false; } if (!super.isReady(completeCheck)) { return false; } if (!this.subMeshes || this.subMeshes.length === 0) { return true; } if (!completeCheck) { return true; } const engine = this.getEngine(); const scene = this.getScene(); const hardwareInstancedRendering = forceInstanceSupport || (engine.getCaps().instancedArrays && (this.instances.length > 0 || this.hasThinInstances)); this.computeWorldMatrix(); const mat = this.material || scene.defaultMaterial; if (mat) { if (mat._storeEffectOnSubMeshes) { for (const subMesh of this.subMeshes) { const effectiveMaterial = subMesh.getMaterial(); if (effectiveMaterial) { if (effectiveMaterial._storeEffectOnSubMeshes) { if (!effectiveMaterial.isReadyForSubMesh(this, subMesh, hardwareInstancedRendering)) { return false; } } else { if (!effectiveMaterial.isReady(this, hardwareInstancedRendering)) { return false; } } } } } else { if (!mat.isReady(this, hardwareInstancedRendering)) { return false; } } } // Shadows const currentRenderPassId = engine.currentRenderPassId; for (const light of this.lightSources) { const generators = light.getShadowGenerators(); if (!generators) { continue; } const iterator = generators.values(); for (let key = iterator.next(); key.done !== true; key = iterator.next()) { const generator = key.value; if (generator && (!generator.getShadowMap()?.renderList || (generator.getShadowMap()?.renderList && generator.getShadowMap()?.renderList?.indexOf(this) !== -1))) { const shadowMap = generator.getShadowMap(); const renderPassIds = shadowMap.renderPassIds ?? [engine.currentRenderPassId]; for (let p = 0; p < renderPassIds.length; ++p) { engine.currentRenderPassId = renderPassIds[p]; for (const subMesh of this.subMeshes) { if (!generator.isReady(subMesh, hardwareInstancedRendering, subMesh.getMaterial()?.needAlphaBlendingForMesh(this) ?? false)) { engine.currentRenderPassId = currentRenderPassId; return false; } } } engine.currentRenderPassId = currentRenderPassId; } } } // LOD for (const lod of this._internalMeshDataInfo._LODLevels) { if (lod.mesh && !lod.mesh.isReady(hardwareInstancedRendering)) { return false; } } return true; } /** * Gets a boolean indicating if the normals aren't to be recomputed on next mesh `positions` array update. This property is pertinent only for updatable parametric shapes. */ get areNormalsFrozen() { return this._internalMeshDataInfo._areNormalsFrozen; } /** * This function affects parametric shapes on vertex position update only : ribbons, tubes, etc. It has no effect at all on other shapes. It prevents the mesh normals from being recomputed on next `positions` array update. * @returns the current mesh */ freezeNormals() { this._internalMeshDataInfo._areNormalsFrozen = true; return this; } /** * This function affects parametric shapes on vertex position update only : ribbons, tubes, etc. It has no effect at all on other shapes. It reactivates the mesh normals computation if it was previously frozen * @returns the current mesh */ unfreezeNormals() { this._internalMeshDataInfo._areNormalsFrozen = false; return this; } /** * Sets a value overriding the instance count. Only applicable when custom instanced InterleavedVertexBuffer are used rather than InstancedMeshs */ set overridenInstanceCount(count) { this._instanceDataStorage.overridenInstanceCount = count; } /** @internal */ _getInstanceDataStorage() { const renderPassId = this._instanceDataStorage.engine.isWebGPU ? this._instanceDataStorage.engine.currentRenderPassId : 0; let instanceDataStorage = this._instanceDataStorage.renderPasses[renderPassId]; if (!instanceDataStorage) { instanceDataStorage = new _InstanceDataStorageRenderPass(); this._instanceDataStorage.renderPasses[renderPassId] = instanceDataStorage; } return instanceDataStorage; } // Methods /** @internal */ _preActivate() { const internalDataInfo = this._internalMeshDataInfo; const sceneRenderId = this.getScene().getRenderId(); if (internalDataInfo._preActivateId === sceneRenderId) { return this; } internalDataInfo._preActivateId = sceneRenderId; this._getInstanceDataStorage().visibleInstances = null; return this; } /** * @internal */ _preActivateForIntermediateRendering(renderId) { const instanceDataStorage = this._getInstanceDataStorage(); if (instanceDataStorage.visibleInstances) { instanceDataStorage.visibleInstances.intermediateDefaultRenderId = renderId; } return this; } /** * @internal */ _registerInstanceForRenderId(instance, renderId) { const instanceDataStorage = this._getInstanceDataStorage(); if (!instanceDataStorage.visibleInstances) { instanceDataStorage.visibleInstances = { defaultRenderId: renderId, selfDefaultRenderId: this._renderId, intermediateDefaultRenderId: -1, }; } if (!instanceDataStorage.visibleInstances[renderId]) { if (instanceDataStorage.previousRenderId !== undefined && this._instanceDataStorage.isFrozen) { instanceDataStorage.visibleInstances[instanceDataStorage.previousRenderId] = null; } instanceDataStorage.previousRenderId = renderId; instanceDataStorage.visibleInstances[renderId] = new Array(); } instanceDataStorage.visibleInstances[renderId].push(instance); return this; } _afterComputeWorldMatrix() { super._afterComputeWorldMatrix(); if (!this.hasThinInstances) { return; } if (!this.doNotSyncBoundingInfo) { this.thinInstanceRefreshBoundingInfo(false); } } /** @internal */ _postActivate() { if (this.edgesShareWithInstances && this.edgesRenderer && this.edgesRenderer.isEnabled && this._renderingGroup) { this._renderingGroup._edgesRenderers.pushNoDuplicate(this.edgesRenderer); this.edgesRenderer.customInstances.push(this.getWorldMatrix()); } } /** * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked. * This means the mesh underlying bounding box and sphere are recomputed. * @param applySkeletonOrOptions defines whether to apply the skeleton before computing the bounding info or a set of options * @param applyMorph defines whether to apply the morph target before computing the bounding info * @returns the current mesh */ refreshBoundingInfo(applySkeletonOrOptions = false, applyMorph = false) {