123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- import { Observable } from "@babylonjs/core/Misc/observable.js";
- import { Deferred } from "@babylonjs/core/Misc/deferred.js";
- import { GLTFLoader, ArrayItem } from "../glTFLoader.js";
- const NAME = "MSFT_lod";
- /**
- * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/MSFT_lod/README.md)
- */
- // eslint-disable-next-line @typescript-eslint/naming-convention
- export class MSFT_lod {
- /**
- * @internal
- */
- constructor(loader) {
- /**
- * The name of this extension.
- */
- this.name = NAME;
- /**
- * Defines a number that determines the order the extensions are applied.
- */
- this.order = 100;
- /**
- * Maximum number of LODs to load, starting from the lowest LOD.
- */
- this.maxLODsToLoad = 10;
- /**
- * 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.
- */
- this.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.
- */
- this.onMaterialLODsLoadedObservable = new Observable();
- this._bufferLODs = new Array();
- this._nodeIndexLOD = null;
- this._nodeSignalLODs = new Array();
- this._nodePromiseLODs = new Array();
- this._nodeBufferLODs = new Array();
- this._materialIndexLOD = null;
- this._materialSignalLODs = new Array();
- this._materialPromiseLODs = new Array();
- this._materialBufferLODs = new Array();
- this._loader = loader;
- this.enabled = this._loader.isExtensionUsed(NAME);
- }
- /** @internal */
- dispose() {
- this._loader = null;
- this._nodeIndexLOD = null;
- this._nodeSignalLODs.length = 0;
- this._nodePromiseLODs.length = 0;
- this._nodeBufferLODs.length = 0;
- this._materialIndexLOD = null;
- this._materialSignalLODs.length = 0;
- this._materialPromiseLODs.length = 0;
- this._materialBufferLODs.length = 0;
- this.onMaterialLODsLoadedObservable.clear();
- this.onNodeLODsLoadedObservable.clear();
- }
- /** @internal */
- onReady() {
- for (let indexLOD = 0; indexLOD < this._nodePromiseLODs.length; indexLOD++) {
- const promise = Promise.all(this._nodePromiseLODs[indexLOD]).then(() => {
- if (indexLOD !== 0) {
- this._loader.endPerformanceCounter(`Node LOD ${indexLOD}`);
- this._loader.log(`Loaded node LOD ${indexLOD}`);
- }
- this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
- if (indexLOD !== this._nodePromiseLODs.length - 1) {
- this._loader.startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
- this._loadBufferLOD(this._nodeBufferLODs, indexLOD + 1);
- if (this._nodeSignalLODs[indexLOD]) {
- this._nodeSignalLODs[indexLOD].resolve();
- }
- }
- });
- this._loader._completePromises.push(promise);
- }
- for (let indexLOD = 0; indexLOD < this._materialPromiseLODs.length; indexLOD++) {
- const promise = Promise.all(this._materialPromiseLODs[indexLOD]).then(() => {
- if (indexLOD !== 0) {
- this._loader.endPerformanceCounter(`Material LOD ${indexLOD}`);
- this._loader.log(`Loaded material LOD ${indexLOD}`);
- }
- this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
- if (indexLOD !== this._materialPromiseLODs.length - 1) {
- this._loader.startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
- this._loadBufferLOD(this._materialBufferLODs, indexLOD + 1);
- if (this._materialSignalLODs[indexLOD]) {
- this._materialSignalLODs[indexLOD].resolve();
- }
- }
- });
- this._loader._completePromises.push(promise);
- }
- }
- /**
- * @internal
- */
- loadSceneAsync(context, scene) {
- const promise = this._loader.loadSceneAsync(context, scene);
- this._loadBufferLOD(this._bufferLODs, 0);
- return promise;
- }
- /**
- * @internal
- */
- loadNodeAsync(context, node, assign) {
- return GLTFLoader.LoadExtensionAsync(context, node, this.name, (extensionContext, extension) => {
- let firstPromise;
- const nodeLODs = this._getLODs(extensionContext, node, this._loader.gltf.nodes, extension.ids);
- this._loader.logOpen(`${extensionContext}`);
- for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
- const nodeLOD = nodeLODs[indexLOD];
- if (indexLOD !== 0) {
- this._nodeIndexLOD = indexLOD;
- this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
- }
- const assignWrap = (babylonTransformNode) => {
- assign(babylonTransformNode);
- babylonTransformNode.setEnabled(false);
- };
- const promise = this._loader.loadNodeAsync(`/nodes/${nodeLOD.index}`, nodeLOD, assignWrap).then((babylonMesh) => {
- if (indexLOD !== 0) {
- // TODO: should not rely on _babylonTransformNode
- const previousNodeLOD = nodeLODs[indexLOD - 1];
- if (previousNodeLOD._babylonTransformNode) {
- this._disposeTransformNode(previousNodeLOD._babylonTransformNode);
- delete previousNodeLOD._babylonTransformNode;
- }
- }
- babylonMesh.setEnabled(true);
- return babylonMesh;
- });
- this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
- if (indexLOD === 0) {
- firstPromise = promise;
- }
- else {
- this._nodeIndexLOD = null;
- this._nodePromiseLODs[indexLOD].push(promise);
- }
- }
- this._loader.logClose();
- return firstPromise;
- });
- }
- /**
- * @internal
- */
- _loadMaterialAsync(context, material, babylonMesh, babylonDrawMode, assign) {
- // Don't load material LODs if already loading a node LOD.
- if (this._nodeIndexLOD) {
- return null;
- }
- return GLTFLoader.LoadExtensionAsync(context, material, this.name, (extensionContext, extension) => {
- let firstPromise;
- const materialLODs = this._getLODs(extensionContext, material, this._loader.gltf.materials, extension.ids);
- this._loader.logOpen(`${extensionContext}`);
- for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
- const materialLOD = materialLODs[indexLOD];
- if (indexLOD !== 0) {
- this._materialIndexLOD = indexLOD;
- }
- const promise = this._loader
- ._loadMaterialAsync(`/materials/${materialLOD.index}`, materialLOD, babylonMesh, babylonDrawMode, (babylonMaterial) => {
- if (indexLOD === 0) {
- assign(babylonMaterial);
- }
- })
- .then((babylonMaterial) => {
- if (indexLOD !== 0) {
- assign(babylonMaterial);
- // TODO: should not rely on _data
- const previousDataLOD = materialLODs[indexLOD - 1]._data;
- if (previousDataLOD[babylonDrawMode]) {
- this._disposeMaterials([previousDataLOD[babylonDrawMode].babylonMaterial]);
- delete previousDataLOD[babylonDrawMode];
- }
- }
- return babylonMaterial;
- });
- this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
- if (indexLOD === 0) {
- firstPromise = promise;
- }
- else {
- this._materialIndexLOD = null;
- this._materialPromiseLODs[indexLOD].push(promise);
- }
- }
- this._loader.logClose();
- return firstPromise;
- });
- }
- /**
- * @internal
- */
- _loadUriAsync(context, property, uri) {
- // Defer the loading of uris if loading a node or material LOD.
- if (this._nodeIndexLOD !== null) {
- this._loader.log(`deferred`);
- const previousIndexLOD = this._nodeIndexLOD - 1;
- this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred();
- return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
- return this._loader.loadUriAsync(context, property, uri);
- });
- }
- else if (this._materialIndexLOD !== null) {
- this._loader.log(`deferred`);
- const previousIndexLOD = this._materialIndexLOD - 1;
- this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred();
- return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
- return this._loader.loadUriAsync(context, property, uri);
- });
- }
- return null;
- }
- /**
- * @internal
- */
- loadBufferAsync(context, buffer, byteOffset, byteLength) {
- if (this._loader.parent.useRangeRequests && !buffer.uri) {
- if (!this._loader.bin) {
- throw new Error(`${context}: Uri is missing or the binary glTF is missing its binary chunk`);
- }
- const loadAsync = (bufferLODs, indexLOD) => {
- const start = byteOffset;
- const end = start + byteLength - 1;
- let bufferLOD = bufferLODs[indexLOD];
- if (bufferLOD) {
- bufferLOD.start = Math.min(bufferLOD.start, start);
- bufferLOD.end = Math.max(bufferLOD.end, end);
- }
- else {
- bufferLOD = { start: start, end: end, loaded: new Deferred() };
- bufferLODs[indexLOD] = bufferLOD;
- }
- return bufferLOD.loaded.promise.then((data) => {
- return new Uint8Array(data.buffer, data.byteOffset + byteOffset - bufferLOD.start, byteLength);
- });
- };
- this._loader.log(`deferred`);
- if (this._nodeIndexLOD !== null) {
- return loadAsync(this._nodeBufferLODs, this._nodeIndexLOD);
- }
- else if (this._materialIndexLOD !== null) {
- return loadAsync(this._materialBufferLODs, this._materialIndexLOD);
- }
- else {
- return loadAsync(this._bufferLODs, 0);
- }
- }
- return null;
- }
- _loadBufferLOD(bufferLODs, indexLOD) {
- const bufferLOD = bufferLODs[indexLOD];
- if (bufferLOD) {
- this._loader.log(`Loading buffer range [${bufferLOD.start}-${bufferLOD.end}]`);
- this._loader.bin.readAsync(bufferLOD.start, bufferLOD.end - bufferLOD.start + 1).then((data) => {
- bufferLOD.loaded.resolve(data);
- }, (error) => {
- bufferLOD.loaded.reject(error);
- });
- }
- }
- /**
- * @returns an array of LOD properties from lowest to highest.
- * @param context
- * @param property
- * @param array
- * @param ids
- */
- _getLODs(context, property, array, ids) {
- if (this.maxLODsToLoad <= 0) {
- throw new Error("maxLODsToLoad must be greater than zero");
- }
- const properties = [];
- for (let i = ids.length - 1; i >= 0; i--) {
- properties.push(ArrayItem.Get(`${context}/ids/${ids[i]}`, array, ids[i]));
- if (properties.length === this.maxLODsToLoad) {
- return properties;
- }
- }
- properties.push(property);
- return properties;
- }
- _disposeTransformNode(babylonTransformNode) {
- const babylonMaterials = [];
- const babylonMaterial = babylonTransformNode.material;
- if (babylonMaterial) {
- babylonMaterials.push(babylonMaterial);
- }
- for (const babylonMesh of babylonTransformNode.getChildMeshes()) {
- if (babylonMesh.material) {
- babylonMaterials.push(babylonMesh.material);
- }
- }
- babylonTransformNode.dispose();
- const babylonMaterialsToDispose = babylonMaterials.filter((babylonMaterial) => this._loader.babylonScene.meshes.every((mesh) => mesh.material != babylonMaterial));
- this._disposeMaterials(babylonMaterialsToDispose);
- }
- _disposeMaterials(babylonMaterials) {
- const babylonTextures = {};
- for (const babylonMaterial of babylonMaterials) {
- for (const babylonTexture of babylonMaterial.getActiveTextures()) {
- babylonTextures[babylonTexture.uniqueId] = babylonTexture;
- }
- babylonMaterial.dispose();
- }
- for (const uniqueId in babylonTextures) {
- for (const babylonMaterial of this._loader.babylonScene.materials) {
- if (babylonMaterial.hasTexture(babylonTextures[uniqueId])) {
- delete babylonTextures[uniqueId];
- }
- }
- }
- for (const uniqueId in babylonTextures) {
- babylonTextures[uniqueId].dispose();
- }
- }
- }
- GLTFLoader.RegisterExtension(NAME, (loader) => new MSFT_lod(loader));
- //# sourceMappingURL=MSFT_lod.js.map
|