@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.
670 lines • 26.9 kB
JavaScript
import { Matrix, TmpVectors } from "../Maths/math.vector.js";
import { Logger } from "../Misc/logger.js";
import { AbstractMesh } from "../Meshes/abstractMesh.js";
import { Mesh } from "../Meshes/mesh.js";
import { DeepCopier } from "../Misc/deepCopier.js";
import { TransformNode } from "./transformNode.js";
import { VertexBuffer } from "../Buffers/buffer.js";
import { Tools } from "../Misc/tools.js";
import { RegisterClass } from "../Misc/typeStore.js";
Mesh._instancedMeshFactory = (name, mesh) => {
const instance = new InstancedMesh(name, mesh);
if (mesh.instancedBuffers) {
instance.instancedBuffers = {};
for (const key in mesh.instancedBuffers) {
instance.instancedBuffers[key] = mesh.instancedBuffers[key];
}
}
return instance;
};
/**
* Creates an instance based on a source mesh.
*/
export class InstancedMesh extends AbstractMesh {
/**
* Creates a new InstancedMesh object from the mesh source.
* @param name defines the name of the instance
* @param source the mesh to create the instance from
*/
constructor(name, source) {
super(name, source.getScene());
/** @internal */
this._indexInSourceMeshInstanceArray = -1;
/** @internal */
this._distanceToCamera = 0;
source.addInstance(this);
this._sourceMesh = source;
this._unIndexed = source._unIndexed;
this.position.copyFrom(source.position);
this.rotation.copyFrom(source.rotation);
this.scaling.copyFrom(source.scaling);
if (source.rotationQuaternion) {
this.rotationQuaternion = source.rotationQuaternion.clone();
}
this.animations = source.animations.slice();
for (const range of source.getAnimationRanges()) {
if (range != null) {
this.createAnimationRange(range.name, range.from, range.to);
}
}
this.infiniteDistance = source.infiniteDistance;
this.setPivotMatrix(source.getPivotMatrix());
if (!source.skeleton && !source.morphTargetManager && source.hasBoundingInfo) {
// without skeleton or morphTargetManager, use bounding info of source mesh directly
const boundingInfo = source.getBoundingInfo();
this.buildBoundingInfo(boundingInfo.minimum, boundingInfo.maximum);
}
else {
this.refreshBoundingInfo(true, true);
}
this._syncSubMeshes();
}
/**
* @returns the string "InstancedMesh".
*/
getClassName() {
return "InstancedMesh";
}
/** Gets the list of lights affecting that mesh */
get lightSources() {
return this._sourceMesh._lightSources;
}
_resyncLightSources() {
// Do nothing as all the work will be done by source mesh
}
_resyncLightSource() {
// Do nothing as all the work will be done by source mesh
}
_removeLightSource() {
// Do nothing as all the work will be done by source mesh
}
// Methods
/**
* If the source mesh receives shadows
*/
get receiveShadows() {
return this._sourceMesh.receiveShadows;
}
set receiveShadows(_value) {
if (this._sourceMesh?.receiveShadows !== _value) {
Tools.Warn("Setting receiveShadows on an instanced mesh has no effect");
}
}
/**
* The material of the source mesh
*/
get material() {
return this._sourceMesh.material;
}
set material(_value) {
if (this._sourceMesh?.material !== _value) {
Tools.Warn("Setting material on an instanced mesh has no effect");
}
}
/**
* Visibility of the source mesh
*/
get visibility() {
return this._sourceMesh.visibility;
}
set visibility(_value) {
if (this._sourceMesh?.visibility !== _value) {
Tools.Warn("Setting visibility on an instanced mesh has no effect");
}
}
/**
* Skeleton of the source mesh
*/
get skeleton() {
return this._sourceMesh.skeleton;
}
set skeleton(_value) {
if (this._sourceMesh?.skeleton !== _value) {
Tools.Warn("Setting skeleton on an instanced mesh has no effect");
}
}
/**
* Rendering ground id of the source mesh
*/
get renderingGroupId() {
return this._sourceMesh.renderingGroupId;
}
set renderingGroupId(value) {
if (!this._sourceMesh || value === this._sourceMesh.renderingGroupId) {
return;
}
//no-op with warning
Logger.Warn("Note - setting renderingGroupId of an instanced mesh has no effect on the scene");
}
/**
* @returns the total number of vertices (integer).
*/
getTotalVertices() {
return this._sourceMesh ? this._sourceMesh.getTotalVertices() : 0;
}
/**
* 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() {
return this._sourceMesh.getTotalIndices();
}
/**
* The source mesh of the instance
*/
get sourceMesh() {
return this._sourceMesh;
}
/**
* Gets the mesh internal Geometry object
*/
get geometry() {
return this._sourceMesh._geometry;
}
/**
* Creates a new InstancedMesh object from the mesh model.
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/copies/instances
* @param name defines the name of the new instance
* @returns a new InstancedMesh
*/
createInstance(name) {
return this._sourceMesh.createInstance(name);
}
/**
* Is this node ready to be used/rendered
* @param completeCheck defines if a complete check (including materials and lights) has to be done (false by default)
* @returns {boolean} is it ready
*/
isReady(completeCheck = false) {
return this._sourceMesh.isReady(completeCheck, true);
}
/**
* Returns an array of integers or a typed array (Int32Array, Uint32Array, Uint16Array) populated with the mesh indices.
* @param kind kind of verticies to retrieve (eg. positions, normals, uvs, etc.)
* @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 forcing the copy of the buffer no matter what the value of copyWhenShared is
* @returns a float array or a Float32Array of the requested kind of data : positions, normals, uvs, etc.
*/
getVerticesData(kind, copyWhenShared, forceCopy) {
return this._sourceMesh.getVerticesData(kind, copyWhenShared, forceCopy);
}
copyVerticesData(kind, vertexData) {
this._sourceMesh.copyVerticesData(kind, vertexData);
}
/**
* Sets the vertex data of the mesh geometry for the requested `kind`.
* If the mesh has no geometry, a new Geometry object is set to the mesh and then passed this vertex data.
* The `data` are either a numeric array either a Float32Array.
* The parameter `updatable` is passed as is to the underlying Geometry object constructor (if initially none) or updater.
* The parameter `stride` is an optional positive integer, it is usually automatically deducted from the `kind` (3 for positions or normals, 2 for UV, etc).
* Note that a new underlying VertexBuffer object is created each call.
* If the `kind` is the `PositionKind`, the mesh BoundingInfo is renewed, so the bounding box and sphere, and the mesh World Matrix is recomputed.
*
* 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
*
* Returns the Mesh.
* @param kind defines vertex data kind
* @param data defines the data source
* @param updatable defines if the data must be flagged as updatable (false as default)
* @param stride defines the vertex stride (optional)
* @returns the current mesh
*/
setVerticesData(kind, data, updatable, stride) {
if (this.sourceMesh) {
this.sourceMesh.setVerticesData(kind, data, updatable, stride);
}
return this.sourceMesh;
}
/**
* Updates the existing vertex data of the mesh geometry for the requested `kind`.
* If the mesh has no geometry, it is simply returned as it is.
* The `data` are either a numeric array either a Float32Array.
* No new underlying VertexBuffer object is created.
* If the `kind` is the `PositionKind` and if `updateExtends` is true, the mesh BoundingInfo is renewed, so the bounding box and sphere, and the mesh World Matrix is recomputed.
* If the parameter `makeItUnique` is true, a new global geometry is created from this positions and is set to the mesh.
*
* 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
*
* Returns the Mesh.
* @param kind defines vertex data kind
* @param data defines the data source
* @param updateExtends defines if extends info of the mesh must be updated (can be null). This is mostly useful for "position" kind
* @param makeItUnique defines it the updated vertex buffer must be flagged as unique (false by default)
* @returns the source mesh
*/
updateVerticesData(kind, data, updateExtends, makeItUnique) {
if (this.sourceMesh) {
this.sourceMesh.updateVerticesData(kind, data, updateExtends, makeItUnique);
}
return this.sourceMesh;
}
/**
* Sets the mesh indices.
* Expects an array populated with integers or a typed array (Int32Array, Uint32Array, Uint16Array).
* If the mesh has no geometry, a new Geometry object is created and set to the mesh.
* This method creates a new index buffer each call.
* Returns the Mesh.
* @param indices the source data
* @param totalVertices defines the total number of vertices referenced by indices (could be null)
* @returns source mesh
*/
setIndices(indices, totalVertices = null) {
if (this.sourceMesh) {
this.sourceMesh.setIndices(indices, totalVertices);
}
return this.sourceMesh;
}
/**
* Boolean : True if the mesh owns the requested kind of data.
* @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
* @returns true if data kind is present
*/
isVerticesDataPresent(kind) {
return this._sourceMesh.isVerticesDataPresent(kind);
}
/**
* @returns an array of indices (IndicesArray).
*/
getIndices() {
return this._sourceMesh.getIndices();
}
get _positions() {
return this._sourceMesh._positions;
}
refreshBoundingInfo(applySkeletonOrOptions = false, applyMorph = false) {
if (this.hasBoundingInfo && this.getBoundingInfo().isLocked) {
return this;
}
let options;
if (typeof applySkeletonOrOptions === "object") {
options = applySkeletonOrOptions;
}
else {
options = {
applySkeleton: applySkeletonOrOptions,
applyMorph: applyMorph,
};
}
const bias = this._sourceMesh.geometry ? this._sourceMesh.geometry.boundingBias : null;
this._refreshBoundingInfo(this._sourceMesh._getData(options, null, VertexBuffer.PositionKind), bias);
return this;
}
/** @internal */
_preActivate() {
if (this._currentLOD) {
this._currentLOD._preActivate();
}
return this;
}
/**
* @internal
*/
_activate(renderId, intermediateRendering) {
super._activate(renderId, intermediateRendering);
if (!this._sourceMesh.subMeshes) {
Logger.Warn("Instances should only be created for meshes with geometry.");
}
if (this._currentLOD) {
const differentSign = this._currentLOD._getWorldMatrixDeterminant() >= 0 !== this._getWorldMatrixDeterminant() >= 0;
if (differentSign) {
this._internalAbstractMeshDataInfo._actAsRegularMesh = true;
return true;
}
this._internalAbstractMeshDataInfo._actAsRegularMesh = false;
this._currentLOD._registerInstanceForRenderId(this, renderId);
if (intermediateRendering) {
if (!this._currentLOD._internalAbstractMeshDataInfo._isActiveIntermediate) {
this._currentLOD._internalAbstractMeshDataInfo._onlyForInstancesIntermediate = true;
return true;
}
}
else {
if (!this._currentLOD._internalAbstractMeshDataInfo._isActive) {
this._currentLOD._internalAbstractMeshDataInfo._onlyForInstances = true;
return true;
}
}
}
return false;
}
/** @internal */
_postActivate() {
if (this._sourceMesh.edgesShareWithInstances && this._sourceMesh._edgesRenderer && this._sourceMesh._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
// we are using the edge renderer of the source mesh
this._sourceMesh._renderingGroup._edgesRenderers.pushNoDuplicate(this._sourceMesh._edgesRenderer);
this._sourceMesh._edgesRenderer.customInstances.push(this.getWorldMatrix());
}
else if (this._edgesRenderer && this._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
// we are using the edge renderer defined for this instance
this._sourceMesh._renderingGroup._edgesRenderers.push(this._edgesRenderer);
}
}
getWorldMatrix() {
if (this._currentLOD &&
this._currentLOD !== this._sourceMesh &&
this._currentLOD.billboardMode !== TransformNode.BILLBOARDMODE_NONE &&
this._currentLOD._masterMesh !== this) {
if (!this._billboardWorldMatrix) {
this._billboardWorldMatrix = new Matrix();
}
const tempMaster = this._currentLOD._masterMesh;
this._currentLOD._masterMesh = this;
TmpVectors.Vector3[7].copyFrom(this._currentLOD.position);
this._currentLOD.position.set(0, 0, 0);
this._billboardWorldMatrix.copyFrom(this._currentLOD.computeWorldMatrix(true));
this._currentLOD.position.copyFrom(TmpVectors.Vector3[7]);
this._currentLOD._masterMesh = tempMaster;
return this._billboardWorldMatrix;
}
return super.getWorldMatrix();
}
get isAnInstance() {
return true;
}
/**
* Returns the current associated LOD AbstractMesh.
* @param camera defines the camera to use to pick the LOD level
* @returns a Mesh or `null` if no LOD is associated with the AbstractMesh
*/
getLOD(camera) {
if (!camera) {
return this;
}
const sourceMeshLODLevels = this.sourceMesh.getLODLevels();
if (!sourceMeshLODLevels || sourceMeshLODLevels.length === 0) {
this._currentLOD = this.sourceMesh;
}
else {
const boundingInfo = this.getBoundingInfo();
this._currentLOD = this.sourceMesh.getLOD(camera, boundingInfo.boundingSphere);
}
return this._currentLOD;
}
/**
* @internal
*/
_preActivateForIntermediateRendering(renderId) {
return this.sourceMesh._preActivateForIntermediateRendering(renderId);
}
/** @internal */
_syncSubMeshes() {
this.releaseSubMeshes();
if (this._sourceMesh.subMeshes) {
for (let index = 0; index < this._sourceMesh.subMeshes.length; index++) {
this._sourceMesh.subMeshes[index].clone(this, this._sourceMesh);
}
}
return this;
}
/** @internal */
_generatePointsArray() {
return this._sourceMesh._generatePointsArray();
}
/** @internal */
_updateBoundingInfo() {
if (this.hasBoundingInfo) {
this.getBoundingInfo().update(this.worldMatrixFromCache);
}
else {
this.buildBoundingInfo(this.absolutePosition, this.absolutePosition, this.worldMatrixFromCache);
}
this._updateSubMeshesBoundingInfo(this.worldMatrixFromCache);
return this;
}
/**
* Creates a new InstancedMesh from the current mesh.
*
* Returns the clone.
* @param name the cloned mesh name
* @param newParent the optional Node to parent the clone to.
* @param doNotCloneChildren if `true` the model children aren't cloned.
* @param newSourceMesh if set this mesh will be used as the source mesh instead of ths instance's one
* @returns the clone
*/
clone(name, newParent = null, doNotCloneChildren, newSourceMesh) {
const result = (newSourceMesh || this._sourceMesh).createInstance(name);
// Deep copy
DeepCopier.DeepCopy(this, result, [
"name",
"subMeshes",
"uniqueId",
"parent",
"lightSources",
"receiveShadows",
"material",
"visibility",
"skeleton",
"sourceMesh",
"isAnInstance",
"facetNb",
"isFacetDataEnabled",
"isBlocked",
"useBones",
"hasInstances",
"collider",
"edgesRenderer",
"forward",
"up",
"right",
"absolutePosition",
"absoluteScaling",
"absoluteRotationQuaternion",
"isWorldMatrixFrozen",
"nonUniformScaling",
"behaviors",
"worldMatrixFromCache",
"hasThinInstances",
"hasBoundingInfo",
"geometry",
], []);
// Parent
if (newParent) {
result.parent = newParent;
}
if (!doNotCloneChildren) {
// Children
for (let index = 0; index < this.getScene().meshes.length; index++) {
const mesh = this.getScene().meshes[index];
if (mesh.parent === this) {
mesh.clone(mesh.name, result);
}
}
}
result.computeWorldMatrix(true);
this.onClonedObservable.notifyObservers(result);
return result;
}
/**
* Disposes the InstancedMesh.
* Returns nothing.
* @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
* @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
*/
dispose(doNotRecurse, disposeMaterialAndTextures = false) {
// Remove from mesh
this._sourceMesh.removeInstance(this);
super.dispose(doNotRecurse, disposeMaterialAndTextures);
}
/**
* @internal
*/
_serializeAsParent(serializationObject) {
super._serializeAsParent(serializationObject);
serializationObject.parentId = this._sourceMesh.uniqueId;
serializationObject.parentInstanceIndex = this._indexInSourceMeshInstanceArray;
}
/**
* Instantiate (when possible) or clone that node with its hierarchy
* @param newParent defines the new parent to use for the instance (or clone)
* @param options defines options to configure how copy is done
* @param options.doNotInstantiate defines if the model must be instantiated or just cloned
* @param options.newSourcedMesh newSourcedMesh the new source mesh for the instance (or clone)
* @param onNewNodeCreated defines an option callback to call when a clone or an instance is created
* @returns an instance (or a clone) of the current node with its hierarchy
*/
instantiateHierarchy(newParent = null, options, onNewNodeCreated) {
const clone = this.clone("Clone of " + (this.name || this.id), newParent || this.parent, true, options && options.newSourcedMesh);
if (clone) {
if (onNewNodeCreated) {
onNewNodeCreated(this, clone);
}
}
for (const child of this.getChildTransformNodes(true)) {
child.instantiateHierarchy(clone, options, onNewNodeCreated);
}
return clone;
}
}
Mesh.prototype.registerInstancedBuffer = function (kind, stride) {
// Remove existing one
this._userInstancedBuffersStorage?.vertexBuffers[kind]?.dispose();
// Creates the instancedBuffer field if not present
if (!this.instancedBuffers) {
this.instancedBuffers = {};
for (const instance of this.instances) {
instance.instancedBuffers = {};
}
}
if (!this._userInstancedBuffersStorage) {
this._userInstancedBuffersStorage = {
data: {},
vertexBuffers: {},
strides: {},
sizes: {},
vertexArrayObjects: this.getEngine().getCaps().vertexArrayObject ? {} : undefined,
};
}
// Creates an empty property for this kind
this.instancedBuffers[kind] = null;
this._userInstancedBuffersStorage.strides[kind] = stride;
this._userInstancedBuffersStorage.sizes[kind] = stride * 32; // Initial size
this._userInstancedBuffersStorage.data[kind] = new Float32Array(this._userInstancedBuffersStorage.sizes[kind]);
this._userInstancedBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._userInstancedBuffersStorage.data[kind], kind, true, false, stride, true);
for (const instance of this.instances) {
instance.instancedBuffers[kind] = null;
}
this._invalidateInstanceVertexArrayObject();
this._markSubMeshesAsAttributesDirty();
};
Mesh.prototype._processInstancedBuffers = function (visibleInstances, renderSelf) {
const instanceCount = visibleInstances ? visibleInstances.length : 0;
for (const kind in this.instancedBuffers) {
let size = this._userInstancedBuffersStorage.sizes[kind];
const stride = this._userInstancedBuffersStorage.strides[kind];
// Resize if required
const expectedSize = (instanceCount + 1) * stride;
while (size < expectedSize) {
size *= 2;
}
if (this._userInstancedBuffersStorage.data[kind].length != size) {
this._userInstancedBuffersStorage.data[kind] = new Float32Array(size);
this._userInstancedBuffersStorage.sizes[kind] = size;
if (this._userInstancedBuffersStorage.vertexBuffers[kind]) {
this._userInstancedBuffersStorage.vertexBuffers[kind].dispose();
this._userInstancedBuffersStorage.vertexBuffers[kind] = null;
}
}
const data = this._userInstancedBuffersStorage.data[kind];
// Update data buffer
let offset = 0;
if (renderSelf) {
const value = this.instancedBuffers[kind];
if (value.toArray) {
value.toArray(data, offset);
}
else if (value.copyToArray) {
value.copyToArray(data, offset);
}
else {
data[offset] = value;
}
offset += stride;
}
for (let instanceIndex = 0; instanceIndex < instanceCount; instanceIndex++) {
const instance = visibleInstances[instanceIndex];
const value = instance.instancedBuffers[kind];
if (value.toArray) {
value.toArray(data, offset);
}
else if (value.copyToArray) {
value.copyToArray(data, offset);
}
else {
data[offset] = value;
}
offset += stride;
}
// Update vertex buffer
if (!this._userInstancedBuffersStorage.vertexBuffers[kind]) {
this._userInstancedBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._userInstancedBuffersStorage.data[kind], kind, true, false, stride, true);
this._invalidateInstanceVertexArrayObject();
}
else {
this._userInstancedBuffersStorage.vertexBuffers[kind].updateDirectly(data, 0);
}
}
};
Mesh.prototype._invalidateInstanceVertexArrayObject = function () {
if (!this._userInstancedBuffersStorage || this._userInstancedBuffersStorage.vertexArrayObjects === undefined) {
return;
}
for (const kind in this._userInstancedBuffersStorage.vertexArrayObjects) {
this.getEngine().releaseVertexArrayObject(this._userInstancedBuffersStorage.vertexArrayObjects[kind]);
}
this._userInstancedBuffersStorage.vertexArrayObjects = {};
};
Mesh.prototype._disposeInstanceSpecificData = function () {
for (const renderPassId in this._instanceDataStorage.renderPasses) {
this._instanceDataStorage.renderPasses[renderPassId].instancesBuffer?.dispose();
}
this._instanceDataStorage.renderPasses = {};
while (this.instances.length) {
this.instances[0].dispose();
}
for (const kind in this.instancedBuffers) {
if (this._userInstancedBuffersStorage.vertexBuffers[kind]) {
this._userInstancedBuffersStorage.vertexBuffers[kind].dispose();
}
}
this._invalidateInstanceVertexArrayObject();
this.instancedBuffers = {};
};
// Register Class Name
RegisterClass("BABYLON.InstancedMesh", InstancedMesh);
//# sourceMappingURL=instancedMesh.js.map