@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
1,212 lines (1,210 loc) • 73.4 kB
JavaScript
import { ap as AbstractMesh, b as Tools, L as Logger, m as VertexBuffer, br as TransformNode, M as Matrix, aS as TmpVectors, bs as DeepCopier, g as Mesh, R as RegisterClass, H as EngineStore, a8 as Light, a2 as Camera, bt as Tags } from './index-FzOfPXLV.esm.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.
*/
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",
], []);
// Bounding info
this.refreshBoundingInfo();
// 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 () {
if (this._instanceDataStorage.instancesBuffer) {
this._instanceDataStorage.instancesBuffer.dispose();
this._instanceDataStorage.instancesBuffer = null;
}
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);
/**
* Root class for AssetContainer and KeepAssets
*/
class AbstractAssetContainer {
constructor() {
/**
* Gets the list of root nodes (ie. nodes with no parent)
*/
this.rootNodes = [];
/** All of the cameras added to this scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/cameras
*/
this.cameras = [];
/**
* All of the lights added to this scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/lights/lights_introduction
*/
this.lights = [];
/**
* All of the (abstract) meshes added to this scene
*/
this.meshes = [];
/**
* The list of skeletons added to the scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons
*/
this.skeletons = [];
/**
* All of the particle systems added to this scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/particle_system_intro
*/
this.particleSystems = [];
/**
* Gets a list of Animations associated with the scene
*/
this.animations = [];
/**
* All of the animation groups added to this scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/animation/groupAnimations
*/
this.animationGroups = [];
/**
* All of the multi-materials added to this scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/multiMaterials
*/
this.multiMaterials = [];
/**
* All of the materials added to this scene
* In the context of a Scene, it is not supposed to be modified manually.
* Any addition or removal should be done using the addMaterial and removeMaterial Scene methods.
* Note also that the order of the Material within the array is not significant and might change.
* @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/materials_introduction
*/
this.materials = [];
/**
* The list of morph target managers added to the scene
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/dynamicMeshMorph
*/
this.morphTargetManagers = [];
/**
* The list of geometries used in the scene.
*/
this.geometries = [];
/**
* All of the transform nodes added to this scene
* In the context of a Scene, it is not supposed to be modified manually.
* Any addition or removal should be done using the addTransformNode and removeTransformNode Scene methods.
* Note also that the order of the TransformNode within the array is not significant and might change.
* @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/transforms/parent_pivot/transform_node
*/
this.transformNodes = [];
/**
* ActionManagers available on the scene.
* @deprecated
*/
this.actionManagers = [];
/**
* Textures to keep.
*/
this.textures = [];
/** @internal */
this._environmentTexture = null;
/**
* The list of postprocesses added to the scene
*/
this.postProcesses = [];
/**
* The list of sounds
*/
this.sounds = null;
/**
* The list of effect layers added to the scene
*/
this.effectLayers = [];
/**
* The list of layers added to the scene
*/
this.layers = [];
/**
* The list of reflection probes added to the scene
*/
this.reflectionProbes = [];
}
/**
* Texture used in all pbr material as the reflection texture.
* As in the majority of the scene they are the same (exception for multi room and so on),
* this is easier to reference from here than from all the materials.
*/
get environmentTexture() {
return this._environmentTexture;
}
set environmentTexture(value) {
this._environmentTexture = value;
}
/**
* @returns all meshes, lights, cameras, transformNodes and bones
*/
getNodes() {
let nodes = [];
nodes = nodes.concat(this.meshes);
nodes = nodes.concat(this.lights);
nodes = nodes.concat(this.cameras);
nodes = nodes.concat(this.transformNodes); // dummies
for (const skeleton of this.skeletons) {
nodes = nodes.concat(skeleton.bones);
}
return nodes;
}
}
/**
* Set of assets to keep when moving a scene into an asset container.
*/
class KeepAssets extends AbstractAssetContainer {
}
/**
* Class used to store the output of the AssetContainer.instantiateAllMeshesToScene function
*/
class InstantiatedEntries {
constructor() {
/**
* List of new root nodes (eg. nodes with no parent)
*/
this.rootNodes = [];
/**
* List of new skeletons
*/
this.skeletons = [];
/**
* List of new animation groups
*/
this.animationGroups = [];
}
/**
* Disposes the instantiated entries from the scene
*/
dispose() {
const rootNodes = this.rootNodes;
for (const rootNode of rootNodes) {
rootNode.dispose();
}
rootNodes.length = 0;
const skeletons = this.skeletons;
for (const skeleton of skeletons) {
skeleton.dispose();
}
skeletons.length = 0;
const animationGroups = this.animationGroups;
for (const animationGroup of animationGroups) {
animationGroup.dispose();
}
animationGroups.length = 0;
}
}
/**
* Container with a set of assets that can be added or removed from a scene.
*/
class AssetContainer extends AbstractAssetContainer {
/**
* Instantiates an AssetContainer.
* @param scene The scene the AssetContainer belongs to.
*/
constructor(scene) {
super();
this._wasAddedToScene = false;
scene = scene || EngineStore.LastCreatedScene;
if (!scene) {
return;
}
this.scene = scene;
this["proceduralTextures"] = [];
scene.onDisposeObservable.add(() => {
if (!this._wasAddedToScene) {
this.dispose();
}
});
this._onContextRestoredObserver = scene.getEngine().onContextRestoredObservable.add(() => {
for (const geometry of this.geometries) {
geometry._rebuild();
}
for (const mesh of this.meshes) {
mesh._rebuild();
}
for (const system of this.particleSystems) {
system.rebuild();
}
for (const texture of this.textures) {
texture._rebuild();
}
});
}
/**
* Given a list of nodes, return a topological sorting of them.
* @param nodes
* @returns a sorted array of nodes
*/
_topologicalSort(nodes) {
const nodesUidMap = new Map();
for (const node of nodes) {
nodesUidMap.set(node.uniqueId, node);
}
const dependencyGraph = {
dependsOn: new Map(), // given a node id, what are the ids of the nodes it depends on
dependedBy: new Map(), // given a node id, what are the ids of the nodes that depend on it
};
// Build the dependency graph given the list of nodes
// First pass: Initialize the empty dependency graph
for (const node of nodes) {
const nodeId = node.uniqueId;
dependencyGraph.dependsOn.set(nodeId, new Set());
dependencyGraph.dependedBy.set(nodeId, new Set());
}
// Second pass: Populate the dependency graph. We assume that we
// don't need to check for cycles here, as the scene graph cannot
// contain cycles. Our graph also already contains all transitive
// dependencies because getDescendants returns the transitive
// dependencies by default.
for (const node of nodes) {
const nodeId = node.uniqueId;
const dependsOn = dependencyGraph.dependsOn.get(nodeId);
if (node instanceof InstancedMesh) {
const masterMesh = node.sourceMesh;
if (nodesUidMap.has(masterMesh.uniqueId)) {
dependsOn.add(masterMesh.uniqueId);
dependencyGraph.dependedBy.get(masterMesh.uniqueId).add(nodeId);
}
}
const dependedBy = dependencyGraph.dependedBy.get(nodeId);
for (const child of node.getDescendants()) {
const childId = child.uniqueId;
if (nodesUidMap.has(childId)) {
dependedBy.add(childId);
const childDependsOn = dependencyGraph.dependsOn.get(childId);
childDependsOn.add(nodeId);
}
}
}
// Third pass: Topological sort
const sortedNodes = [];
// First: Find all nodes that have no dependencies
const leaves = [];
for (const node of nodes) {
const nodeId = node.uniqueId;
if (dependencyGraph.dependsOn.get(nodeId).size === 0) {
leaves.push(node);
nodesUidMap.delete(nodeId);
}
}
const visitList = leaves;
while (visitList.length > 0) {
const nodeToVisit = visitList.shift();
sortedNodes.push(nodeToVisit);
// Remove the node from the dependency graph
// When a node is visited, we know that dependsOn is empty.
// So we only need to remove the node from dependedBy.
const dependedByVisitedNode = dependencyGraph.dependedBy.get(nodeToVisit.uniqueId);
// Array.from(x.values()) is to make the TS compiler happy
for (const dependedByVisitedNodeId of Array.from(dependedByVisitedNode.values())) {
const dependsOnDependedByVisitedNode = dependencyGraph.dependsOn.get(dependedByVisitedNodeId);
dependsOnDependedByVisitedNode.delete(nodeToVisit.uniqueId);
if (dependsOnDependedByVisitedNode.size === 0 && nodesUidMap.get(dependedByVisitedNodeId)) {
visitList.push(nodesUidMap.get(dependedByVisitedNodeId));
nodesUidMap.delete(dependedByVisitedNodeId);
}
}
}
if (nodesUidMap.size > 0) {
Logger.Error("SceneSerializer._topologicalSort: There were unvisited nodes:");
nodesUidMap.forEach((node) => {
Logger.Error(node.name);
});
}
return sortedNodes;
}
_addNodeAndDescendantsToList(list, addedIds, rootNode, predicate) {
if (!rootNode || (predicate && !predicate(rootNode)) || addedIds.has(rootNode.uniqueId)) {
return;
}
list.push(rootNode);
addedIds.add(rootNode.uniqueId);
for (const child of rootNode.getDescendants(true)) {
this._addNodeAndDescendantsToList(list, addedIds, child, predicate);
}
}
/**
* Check if a specific node is contained in this asset container.
* @param node the node to check
* @returns true if the node is contained in this container, otherwise false.
*/
_isNodeInContainer(node) {
if (node instanceof AbstractMesh && this.meshes.indexOf(node) !== -1) {
return true;
}
if (node instanceof TransformNode && this.transformNodes.indexOf(node) !== -1) {
return true;
}
if (node instanceof Light && this.lights.indexOf(node) !== -1) {
return true;
}
if (node instanceof Camera && this.cameras.indexOf(node) !== -1) {
return true;
}
return false;
}
/**
* For every node in the scene, check if its parent node is also in the scene.
* @returns true if every node's parent is also in the scene, otherwise false.
*/
_isValidHierarchy() {
for (const node of this.meshes) {
if (node.parent && !this._isNodeInContainer(node.parent)) {
Logger.Warn(`Node ${node.name} has a parent that is not in the container.`);
return false;
}
}
for (const node of this.transformNodes) {
if (node.parent && !this._isNodeInContainer(node.parent)) {
Logger.Warn(`Node ${node.name} has a parent that is not in the container.`);
return false;
}
}
for (const node of this.lights) {
if (node.parent && !this._isNodeInContainer(node.parent)) {
Logger.Warn(`Node ${node.name} has a parent that is not in the container.`);
return false;
}
}
for (const node of this.cameras) {
if (node.parent && !this._isNodeInContainer(node.parent)) {
Logger.Warn(`Node ${node.name} has a parent that is not in the container.`);
return false;
}
}
return true;
}
/**
* Instantiate or clone all meshes and add the new ones to the scene.
* Skeletons and animation groups will all be cloned
* @param nameFunction defines an optional function used to get new names for clones
* @param cloneMaterials defines an optional boolean that defines if materials must be cloned as well (false by default)
* @param options defines an optional list of options to control how to instantiate / clone models
* @param options.doNotInstantiate defines if the model must be instantiated or just cloned
* @param options.predicate defines a predicate used to filter whih mesh to instantiate/clone
* @returns a list of rootNodes, skeletons and animation groups that were duplicated
*/
instantiateModelsToScene(nameFunction, cloneMaterials = false, options) {
if (!this._isValidHierarchy()) {
Tools.Warn("SceneSerializer.InstantiateModelsToScene: The Asset Container hierarchy is not valid.");
}
const conversionMap = {};
const storeMap = {};
const result = new InstantiatedEntries();
const alreadySwappedSkeletons = [];
const alreadySwappedMaterials = [];
const localOptions = {
doNotInstantiate: true,
...options,
};
const onClone = (source, clone) => {
conversionMap[source.uniqueId] = clone.uniqueId;
storeMap[clone.uniqueId] = clone;
if (nameFunction) {
clone.name = nameFunction(source.name);
}
if (clone instanceof Mesh) {
const clonedMesh = clone;
if (clonedMesh.morphTargetManager) {
const oldMorphTargetManager = source.morphTargetManager;
clonedMesh.morphTargetManager = oldMorphTargetManager.clone();
for (let index = 0; index < oldMorphTargetManager.numTargets; index++) {
const oldTarget = oldMorphTargetManager.getTarget(index);
const newTarget = clonedMesh.morphTargetManager.getTarget(index);
conversionMap[oldTarget.uniqueId] = newTarget.uniqueId;
storeMap[newTarget.uniqueId] = newTarget;
}
}
}
};
const nodesToSort = [];
const idsOnSortList = new Set();
for (const transformNode of this.transformNodes) {
if (transformNode.parent === null) {
this._addNodeAndDescendantsToList(nodesToSort, idsOnSortList, transformNode, localOptions.predicate);
}
}
for (const mesh of this.meshes) {
if (mesh.parent === null) {
this._addNodeAndDescendantsToList(nodesToSort, idsOnSortList, mesh, localOptions.predicate);
}
}
// Topologically sort nodes by parenting/instancing relationships so that all resources are in place
// when a given node is instantiated.
const sortedNodes = this._topologicalSort(nodesToSort);
const onNewCreated = (source, clone) => {
onClone(source, clone);
if (source.parent) {
const replicatedParentId = conversionMap[source.parent.uniqueId];
const replicatedParent = storeMap[replicatedParentId];
if (replicatedParent) {
clone.parent = replicatedParent;
}
else {
clone.parent = source.parent;
}
}
if (clone.position && source.position) {
clone.position.copyFrom(source.position);
}
if (clone.rotationQuaternion && source.rotationQuaternion) {
clone.rotationQuaternion.copyFrom(source.rotationQuaternion);
}
if (clone.rotation && source.rotation) {
clone.rotation.copyFrom(source.rotation);
}
if (clone.scaling && source.scaling) {
clone.scaling.copyFrom(source.scaling);
}
if (clone.material) {
const mesh = clone;
if (mesh.material) {
if (cloneMaterials) {
const sourceMaterial = source.material;
if (alreadySwappedMaterials.indexOf(sourceMaterial) === -1) {
let swap = sourceMaterial.clone(nameFunction ? nameFunction(sourceMaterial.name) : "Clone of " + sourceMaterial.name);
alreadySwappedMaterials.push(sourceMaterial);
conversionMap[sourceMaterial.uniqueId] = swap.uniqueId;
storeMap[swap.uniqueId] = swap;
if (sourceMaterial.getClassName() === "MultiMaterial") {
const multi = sourceMaterial;
for (const material of multi.subMaterials) {
if (!material) {
continue;
}
swap = material.clone(nameFunction ? nameFunction(material.name) : "Clone of " + material.name);
alreadySwappedMaterials.push(material);
conversionMap[material.uniqueId] = swap.uniqueId;
storeMap[swap.uniqueId] = swap;
}
multi.subMaterials = multi.subMaterials.map((m) => m && storeMap[conversionMap[m.uniqueId]]);
}
}
if (mesh.getClassName() !== "InstancedMesh") {
mesh.material = storeMap[conversionMap[sourceMaterial.uniqueId]];
}
}
else {
if (mesh.material.getClassName() === "MultiMaterial") {
if (this.scene.multiMaterials.indexOf(mesh.material) === -1) {
this.scene.addMultiMaterial(mesh.material);
}
}
else {
if (this.scene.materials.indexOf(mesh.material) === -1) {
this.scene.addMaterial(mesh.material);
}
}
}
}
}
if (clone.parent === null) {
result.rootNodes.push(clone);
}
};
for (const node of sortedNodes) {
if (node.getClassName() === "InstancedMesh") {
const instancedNode = node;
const sourceMesh = instancedNode.sourceMesh;
const replicatedSourceId = conversionMap[sourceMesh.uniqueId];
const replicatedSource = typeof replicatedSourceId === "number" ? storeMap[replicatedSourceId] : sourceMesh;
const replicatedInstancedNode = replicatedSource.createInstance(instancedNode.name);
onNewCreated(instancedNode, replicatedInstancedNode);
}
else {
// Mesh or TransformNode
let canInstance = true;
if (node.getClassName() === "TransformNode" ||
node.getClassName() === "Node" ||
node.skeleton ||
!node.getTotalVertices ||
node.getTotalVertices() === 0) {
// Transform nodes, skinned meshes, and meshes with no vertices can never be instanced!
canInstance = false;
}
else if (localOptions.doNotInstantiate) {
if (typeof localOptions.doNotInstantiate === "function") {
canInstance = !localOptions.doNotInstantiate(node);
}
else {
canInstance = !localOptions.doNotInstantiate;
}
}
const replicatedNode = canInstance ? node.createInstance(`instance of ${node.name}`) : node.clone(`Clone of ${node.name}`, null, true);
if (!replicatedNode) {
throw new Error(`Could not clone or instantiate node on Asset Container ${node.name}`);
}
onNewCreated(node, replicatedNode);
}
}
for (const s of this.skeletons) {
if (localOptions.predicate && !localOptions.predicate(s)) {
continue;
}
const clone = s.clone(nameFunction ? nameFunction(s.name) : "Clone of " + s.name);
for (const m of this.meshes) {
if (m.skeleton === s && !m.isAnInstance) {
const copy = storeMap[conversionMap[m.uniqueId]];
if (!copy || copy.isAnInstance) {
continue;
}
copy.skeleton = clone;
if (alreadySwappedSkeletons.indexOf(clone) !== -1) {
continue;
}
alreadySwappedSkeletons.push(clone);
// Check if bones are mesh linked
for (const bone of clone.bones) {
if (bone._linkedTransformNode) {
bone._linkedTransformNode = storeMap[conversionMap[bone._linkedTransformNode.uniqueId]];
}
}
}
}
result.skeletons.push(clone);
}
for (const o of this.animationGroups) {
if (localOptions.predicate && !localOptions.predicate(o)) {
continue;
}