///
module BABYLON.GLTF2.Extensions {
const NAME = "MSFT_lod";
interface IMSFTLOD {
ids: number[];
}
/**
* [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod)
*/
export class MSFT_lod extends GLTFLoaderExtension {
public readonly name = NAME;
/**
* Maximum number of LODs to load, starting from the lowest LOD.
*/
public maxLODsToLoad = Number.MAX_VALUE;
/**
* Observable raised when all node LODs of one level are loaded.
* The event data is the index of the loaded LOD starting from zero.
* Dispose the loader to cancel the loading of the next level of LODs.
*/
public onNodeLODsLoadedObservable = new Observable();
/**
* Observable raised when all material LODs of one level are loaded.
* The event data is the index of the loaded LOD starting from zero.
* Dispose the loader to cancel the loading of the next level of LODs.
*/
public onMaterialLODsLoadedObservable = new Observable();
private _loadingNodeLOD: Nullable<_ILoaderNode> = null;
private _loadNodeSignals: { [nodeIndex: number]: Deferred } = {};
private _loadNodePromises = new Array>>();
private _loadingMaterialLOD: Nullable<_ILoaderMaterial> = null;
private _loadMaterialSignals: { [materialIndex: number]: Deferred } = {};
private _loadMaterialPromises = new Array>>();
constructor(loader: GLTFLoader) {
super(loader);
this._loader._onReadyObservable.addOnce(() => {
for (let indexLOD = 0; indexLOD < this._loadNodePromises.length; indexLOD++) {
Promise.all(this._loadNodePromises[indexLOD]).then(() => {
this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
});
}
for (let indexLOD = 0; indexLOD < this._loadMaterialPromises.length; indexLOD++) {
Promise.all(this._loadMaterialPromises[indexLOD]).then(() => {
this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
});
}
});
}
public dispose() {
super.dispose();
this._loadingNodeLOD = null;
this._loadNodeSignals = {};
this._loadingMaterialLOD = null;
this._loadMaterialSignals = {};
this.onMaterialLODsLoadedObservable.clear();
this.onNodeLODsLoadedObservable.clear();
}
protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable> {
return this._loadExtensionAsync(context, node, (extensionContext, extension) => {
let firstPromise: Promise;
const nodeLODs = this._getLODs(extensionContext, node, this._loader._gltf.nodes, extension.ids);
for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
const nodeLOD = nodeLODs[indexLOD];
if (indexLOD !== 0) {
this._loadingNodeLOD = nodeLOD;
if (!this._loadNodeSignals[nodeLOD._index]) {
this._loadNodeSignals[nodeLOD._index] = new Deferred();
}
}
const promise = this._loader._loadNodeAsync(`#/nodes/${nodeLOD._index}`, nodeLOD).then(() => {
if (indexLOD !== 0) {
const previousNodeLOD = nodeLODs[indexLOD - 1];
if (previousNodeLOD._babylonMesh) {
previousNodeLOD._babylonMesh.dispose(false, true);
delete previousNodeLOD._babylonMesh;
}
}
if (indexLOD !== nodeLODs.length - 1) {
const nodeIndex = nodeLODs[indexLOD + 1]._index;
if (this._loadNodeSignals[nodeIndex]) {
this._loadNodeSignals[nodeIndex].resolve();
delete this._loadNodeSignals[nodeIndex];
}
}
});
if (indexLOD === 0) {
firstPromise = promise;
}
else {
this._loader._completePromises.push(promise);
this._loadingNodeLOD = null;
}
this._loadNodePromises[indexLOD] = this._loadNodePromises[indexLOD] || [];
this._loadNodePromises[indexLOD].push(promise);
}
return firstPromise!;
});
}
protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable> {
// Don't load material LODs if already loading a node LOD.
if (this._loadingNodeLOD) {
return null;
}
return this._loadExtensionAsync(context, material, (extensionContext, extension) => {
let firstPromise: Promise;
const materialLODs = this._getLODs(extensionContext, material, this._loader._gltf.materials, extension.ids);
for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
const materialLOD = materialLODs[indexLOD];
if (indexLOD !== 0) {
this._loadingMaterialLOD = materialLOD;
if (!this._loadMaterialSignals[materialLOD._index]) {
this._loadMaterialSignals[materialLOD._index] = new Deferred();
}
}
const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD._index}`, materialLOD, mesh, babylonMesh, babylonDrawMode, indexLOD === 0 ? assign : () => {}).then(() => {
if (indexLOD !== 0) {
const babylonDataLOD = materialLOD._babylonData!;
assign(babylonDataLOD[babylonDrawMode].material);
const previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData!;
if (previousBabylonDataLOD[babylonDrawMode]) {
previousBabylonDataLOD[babylonDrawMode].material.dispose();
delete previousBabylonDataLOD[babylonDrawMode];
}
}
if (indexLOD !== materialLODs.length - 1) {
const materialIndex = materialLODs[indexLOD + 1]._index;
if (this._loadMaterialSignals[materialIndex]) {
this._loadMaterialSignals[materialIndex].resolve();
delete this._loadMaterialSignals[materialIndex];
}
}
});
if (indexLOD === 0) {
firstPromise = promise;
}
else {
this._loader._completePromises.push(promise);
this._loadingMaterialLOD = null;
}
this._loadMaterialPromises[indexLOD] = this._loadMaterialPromises[indexLOD] || [];
this._loadMaterialPromises[indexLOD].push(promise);
}
return firstPromise!;
});
}
protected _loadUriAsync(context: string, uri: string): Nullable> {
// Defer the loading of uris if loading a material or node LOD.
if (this._loadingMaterialLOD) {
const index = this._loadingMaterialLOD._index;
return this._loadMaterialSignals[index].promise.then(() => {
return this._loader._loadUriAsync(context, uri);
});
}
else if (this._loadingNodeLOD) {
const index = this._loadingNodeLOD._index;
return this._loadNodeSignals[index].promise.then(() => {
return this._loader._loadUriAsync(context, uri);
});
}
return null;
}
/**
* Gets an array of LOD properties from lowest to highest.
*/
private _getLODs(context: string, property: T, array: ArrayLike | undefined, ids: number[]): T[] {
if (this.maxLODsToLoad <= 0) {
throw new Error("maxLODsToLoad must be greater than zero");
}
const properties = new Array();
for (let i = ids.length - 1; i >= 0; i--) {
properties.push(GLTFLoader._GetProperty(`${context}/ids/${ids[i]}`, array, ids[i]));
if (properties.length === this.maxLODsToLoad) {
return properties;
}
}
properties.push(property);
return properties;
}
}
GLTFLoader._Register(NAME, loader => new MSFT_lod(loader));
}