@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
JavaScript
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) {