Browse Source

Merge pull request #8329 from bghgary/gltf-progress-fixes

Improve progress handling in glTF loader
David Catuhe 5 năm trước cách đây
mục cha
commit
66cc6b19ec

+ 2 - 2
Viewer/src/loader/plugins/loaderPlugin.ts

@@ -1,7 +1,7 @@
 import { ViewerModel } from "../../model/viewerModel";
 import { IGLTFLoaderExtension, IGLTFLoaderData } from "babylonjs-loaders/glTF/glTFFileLoader";
 import { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from "babylonjs/Loading/sceneLoader";
-import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+import { ISceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { Material } from "babylonjs/Materials/material";
@@ -15,7 +15,7 @@ export interface ILoaderPlugin {
     onInit?: (loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync, model: ViewerModel) => void;
     onLoaded?: (model: ViewerModel) => void;
     onError?: (message: string, exception?: any) => void;
-    onProgress?: (progressEvent: SceneLoaderProgressEvent) => void;
+    onProgress?: (progressEvent: ISceneLoaderProgressEvent) => void;
     onExtensionLoaded?: (extension: IGLTFLoaderExtension) => void;
     onParsed?: (parsedData: IGLTFLoaderData) => void;
     onMeshLoaded?: (mesh: AbstractMesh) => void;

+ 2 - 2
Viewer/src/managers/observablesManager.ts

@@ -1,7 +1,7 @@
 import { Observable } from 'babylonjs/Misc/observable';
 import { Scene } from 'babylonjs/scene';
 import { Engine } from 'babylonjs/Engines/engine';
-import { SceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync } from 'babylonjs/Loading/sceneLoader';
+import { ISceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync } from 'babylonjs/Loading/sceneLoader';
 
 import { ViewerModel } from '../model/viewerModel';
 
@@ -28,7 +28,7 @@ export class ObservablesManager {
     /**
      * will notify when any model notify of progress
      */
-    public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    public onModelLoadProgressObservable: Observable<ISceneLoaderProgressEvent>;
     /**
      * will notify when any model load failed.
      */

+ 2 - 2
Viewer/src/model/viewerModel.ts

@@ -4,7 +4,7 @@ import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
 import { Skeleton } from "babylonjs/Bones/skeleton";
 import { Observable } from "babylonjs/Misc/observable";
-import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+import { ISceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 import { Animation, Animatable, CircleEase, BackEase, BounceEase, CubicEase, ElasticEase, ExponentialEase, PowerEase, QuadraticEase, QuarticEase, QuinticEase, SineEase } from "babylonjs/Animations/index";
 import { Nullable } from "babylonjs/types";
@@ -81,7 +81,7 @@ export class ViewerModel implements IDisposable {
     /**
      * Observers registered here will be executed when the loader notified of a progress event
      */
-    public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    public onLoadProgressObservable: Observable<ISceneLoaderProgressEvent>;
     /**
      * Observers registered here will be executed when the loader notified of an error.
      */

+ 2 - 2
Viewer/src/viewer/viewer.ts

@@ -1,5 +1,5 @@
 import { Engine } from 'babylonjs/Engines/engine';
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderProgressEvent } from 'babylonjs/Loading/sceneLoader';
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, ISceneLoaderProgressEvent } from 'babylonjs/Loading/sceneLoader';
 import { Observable } from 'babylonjs/Misc/observable';
 import { Scene } from 'babylonjs/scene';
 import { RenderingManager } from 'babylonjs/Rendering/renderingManager';
@@ -85,7 +85,7 @@ export abstract class AbstractViewer {
     /**
      * will notify when any model notify of progress
      */
-    public get onModelLoadProgressObservable(): Observable<SceneLoaderProgressEvent> {
+    public get onModelLoadProgressObservable(): Observable<ISceneLoaderProgressEvent> {
         return this.observablesManager.onModelLoadProgressObservable;
     }
     /**

+ 2 - 0
dist/preview release/what's new.md

@@ -94,6 +94,7 @@
 - Added support for KHR_xmp for glTF loader. ([Sebavan](https://github.com/sebavan/))
 - Added support for KHR_materials_variants for glTF loader. ([MiiBond](https://github.com/MiiBond/))
 - Added support for KHR_materials_transmission for glTF loader. ([MiiBond](https://github.com/MiiBond/))
+- Improved progress handling in glTF loader. ([bghgary](https://github.com/bghgary))
 
 ### Navigation
 
@@ -231,3 +232,4 @@
 - Sound's `updateOptions` takes `options.length` and `options.offset` as seconds and not milliseconds ([RaananW](https://github.com/RaananW))
 - HDRCubeTexture default rotation is now similar to the industry one. You might need to add a rotation on y of 90 degrees if you scene changes ([Sebavan](https://github.com/sebavan/))
 - PBRMaterial index of refraction is now defined as index of refraction and not the inverse of it ([Sebavan](https://github.com/sebavan/))
+- `SceneLoaderProgress` class is now `ISceneLoaderProgress` interface ([bghgary](https://github.com/bghgary))

+ 4 - 4
loaders/src/OBJ/objFileLoader.ts

@@ -9,7 +9,7 @@ import { Skeleton } from "babylonjs/Bones/skeleton";
 import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
-import { SceneLoader, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
+import { SceneLoader, ISceneLoaderPluginAsync, ISceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
 
 import { AssetContainer } from "babylonjs/assetContainer";
 import { Scene } from "babylonjs/scene";
@@ -236,7 +236,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlugi
      * @param fileName Defines the name of the file to load
      * @returns a promise containg the loaded meshes, particles, skeletons and animations
      */
-    public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
+    public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
         //get the meshes from OBJ file
         return this._parseSolid(meshesNames, scene, data, rootUrl).then((meshes) => {
             return {
@@ -257,7 +257,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlugi
      * @param fileName Defines the name of the file to load
      * @returns a promise which completes when objects have been loaded to the scene
      */
-    public loadAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
+    public loadAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
         //Get the 3D model
         return this.importMeshAsync(null, scene, data, rootUrl, onProgress).then(() => {
             // return void
@@ -273,7 +273,7 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlugi
      * @param fileName Defines the name of the file to load
      * @returns The loaded asset container
      */
-    public loadAssetContainerAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
+    public loadAssetContainerAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
         this._forAssetContainer = true;
 
         return this.importMeshAsync(null, scene, data, rootUrl).then((result) => {

+ 6 - 6
loaders/src/glTF/1.0/glTFLoader.ts

@@ -26,7 +26,7 @@ import { HemisphericLight } from "babylonjs/Lights/hemisphericLight";
 import { DirectionalLight } from "babylonjs/Lights/directionalLight";
 import { PointLight } from "babylonjs/Lights/pointLight";
 import { SpotLight } from "babylonjs/Lights/spotLight";
-import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+import { ISceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { Scene } from "babylonjs/scene";
 
 import { GLTFUtils } from "./glTFLoaderUtils";
@@ -1621,7 +1621,7 @@ export class GLTFLoader implements IGLTFLoader {
         // do nothing
     }
 
-    private _importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, forAssetContainer: boolean, onSuccess: (meshes: AbstractMesh[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string) => void): boolean {
+    private _importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, forAssetContainer: boolean, onSuccess: (meshes: AbstractMesh[], skeletons: Skeleton[]) => void, onProgress?: (event: ISceneLoaderProgressEvent) => void, onError?: (message: string) => void): boolean {
         scene.useRightHandedSystem = true;
 
         GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, (gltfRuntime) => {
@@ -1695,7 +1695,7 @@ export class GLTFLoader implements IGLTFLoader {
     * @param onProgress event that fires when loading progress has occured
     * @returns a promise containg the loaded meshes, particles, skeletons and animations
     */
-    public importMeshAsync(meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<IImportMeshAsyncOutput> {
+    public importMeshAsync(meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void): Promise<IImportMeshAsyncOutput> {
         return new Promise((resolve, reject) => {
             this._importMeshAsync(meshesNames, scene, data, rootUrl, forAssetContainer, (meshes, skeletons) => {
                 resolve({
@@ -1712,7 +1712,7 @@ export class GLTFLoader implements IGLTFLoader {
         });
     }
 
-    private _loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, forAssetContainer: boolean, onSuccess: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string) => void): void {
+    private _loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, forAssetContainer: boolean, onSuccess: () => void, onProgress?: (event: ISceneLoaderProgressEvent) => void, onError?: (message: string) => void): void {
         scene.useRightHandedSystem = true;
 
         GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, (gltfRuntime) => {
@@ -1748,7 +1748,7 @@ export class GLTFLoader implements IGLTFLoader {
     * @param onProgress event that fires when loading progress has occured
     * @returns a promise which completes when objects have been loaded to the scene
     */
-    public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
+    public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void): Promise<void> {
         return new Promise((resolve, reject) => {
             this._loadAsync(scene, data, rootUrl, false, () => {
                 resolve();
@@ -1798,7 +1798,7 @@ export class GLTFLoader implements IGLTFLoader {
         }
     }
 
-    private _loadBuffersAsync(gltfRuntime: IGLTFRuntime, onLoad: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void): void {
+    private _loadBuffersAsync(gltfRuntime: IGLTFRuntime, onLoad: () => void, onProgress?: (event: ISceneLoaderProgressEvent) => void): void {
         var hasBuffers = false;
 
         var processBuffer = (buf: string, buffer: IGLTFBuffer) => {

+ 1 - 0
loaders/src/glTF/2.0/Extensions/EXT_lights_image_based.ts

@@ -117,6 +117,7 @@ export class EXT_lights_image_based implements IGLTFLoaderExtension {
 
             light._loaded = Promise.all(promises).then(() => {
                 const babylonTexture = new RawCubeTexture(this._loader.babylonScene, null, light.specularImageSize);
+                babylonTexture.name = light.name || "environment";
                 light._babylonTexture = babylonTexture;
 
                 if (light.intensity != undefined) {

+ 68 - 60
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -16,6 +16,12 @@ interface IMSFTLOD {
     ids: number[];
 }
 
+interface IBufferInfo {
+    start: number;
+    end: number;
+    loaded: Deferred<ArrayBufferView>;
+}
+
 /**
  * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod)
  */
@@ -56,16 +62,17 @@ export class MSFT_lod implements IGLTFLoaderExtension {
 
     private _loader: GLTFLoader;
 
+    private _bufferLODs = new Array<IBufferInfo>();
+
     private _nodeIndexLOD: Nullable<number> = null;
     private _nodeSignalLODs = new Array<Deferred<void>>();
     private _nodePromiseLODs = new Array<Array<Promise<any>>>();
+    private _nodeBufferLODs = new Array<IBufferInfo>();
 
     private _materialIndexLOD: Nullable<number> = null;
     private _materialSignalLODs = new Array<Deferred<void>>();
     private _materialPromiseLODs = new Array<Array<Promise<any>>>();
-
-    private _indexLOD: Nullable<number> = null;
-    private _bufferLODs = new Array<{ start: number, end: number, loaded: Deferred<ArrayBufferView> }>();
+    private _materialBufferLODs = new Array<IBufferInfo>();
 
     /** @hidden */
     constructor(loader: GLTFLoader) {
@@ -80,13 +87,12 @@ export class MSFT_lod implements IGLTFLoaderExtension {
         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._indexLOD = null;
-        this._bufferLODs.length = 0;
+        this._materialBufferLODs.length = 0;
 
         this.onMaterialLODsLoadedObservable.clear();
         this.onNodeLODsLoadedObservable.clear();
@@ -98,13 +104,14 @@ export class MSFT_lod implements IGLTFLoaderExtension {
             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._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();
                     }
@@ -118,13 +125,14 @@ export class MSFT_lod implements IGLTFLoaderExtension {
             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._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();
                     }
@@ -133,18 +141,12 @@ export class MSFT_lod implements IGLTFLoaderExtension {
 
             this._loader._completePromises.push(promise);
         }
-
-        for (let indexLOD = 1; indexLOD < this._bufferLODs.length; indexLOD++) {
-            this._loadBufferLOD(indexLOD);
-        }
     }
 
     /** @hidden */
     public loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>> {
         const promise = this._loader.loadSceneAsync(context, scene);
-        if (this._bufferLODs.length !== 0) {
-            this._loadBufferLOD(0);
-        }
+        this._loadBufferLOD(this._bufferLODs, 0);
         return promise;
     }
 
@@ -159,8 +161,6 @@ export class MSFT_lod implements IGLTFLoaderExtension {
             for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
                 const nodeLOD = nodeLODs[indexLOD];
 
-                this._indexLOD = indexLOD;
-
                 if (indexLOD !== 0) {
                     this._nodeIndexLOD = indexLOD;
                     this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
@@ -181,17 +181,15 @@ export class MSFT_lod implements IGLTFLoaderExtension {
                     return babylonMesh;
                 });
 
+                this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
+
                 if (indexLOD === 0) {
                     firstPromise = promise;
                 }
                 else {
                     this._nodeIndexLOD = null;
+                    this._nodePromiseLODs[indexLOD].push(promise);
                 }
-
-                this._indexLOD = null;
-
-                this._nodePromiseLODs[indexLOD] = this._nodePromiseLODs[indexLOD] || [];
-                this._nodePromiseLODs[indexLOD].push(promise);
             }
 
             this._loader.logClose();
@@ -202,7 +200,7 @@ export class MSFT_lod implements IGLTFLoaderExtension {
     /** @hidden */
     public _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>> {
         // Don't load material LODs if already loading a node LOD.
-        if (this._indexLOD) {
+        if (this._nodeIndexLOD) {
             return null;
         }
 
@@ -215,8 +213,6 @@ export class MSFT_lod implements IGLTFLoaderExtension {
             for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
                 const materialLOD = materialLODs[indexLOD];
 
-                this._indexLOD = indexLOD;
-
                 if (indexLOD !== 0) {
                     this._materialIndexLOD = indexLOD;
                 }
@@ -240,17 +236,15 @@ export class MSFT_lod implements IGLTFLoaderExtension {
                     return babylonMaterial;
                 });
 
+                this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
+
                 if (indexLOD === 0) {
                     firstPromise = promise;
                 }
                 else {
                     this._materialIndexLOD = null;
+                    this._materialPromiseLODs[indexLOD].push(promise);
                 }
-
-                this._indexLOD = null;
-
-                this._materialPromiseLODs[indexLOD] = this._materialPromiseLODs[indexLOD] || [];
-                this._materialPromiseLODs[indexLOD].push(promise);
             }
 
             this._loader.logClose();
@@ -260,20 +254,20 @@ export class MSFT_lod implements IGLTFLoaderExtension {
 
     /** @hidden */
     public _loadUriAsync(context: string, property: IProperty, uri: string): Nullable<Promise<ArrayBufferView>> {
-        // Defer the loading of uris if loading a material or node LOD.
-        if (this._materialIndexLOD !== null) {
+        // Defer the loading of uris if loading a node or material LOD.
+        if (this._nodeIndexLOD !== null) {
             this._loader.log(`deferred`);
-            const previousIndexLOD = this._materialIndexLOD - 1;
-            this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
-            return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
+            const previousIndexLOD = this._nodeIndexLOD - 1;
+            this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
+            return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
                 return this._loader.loadUriAsync(context, property, uri);
             });
         }
-        else if (this._nodeIndexLOD !== null) {
+        else if (this._materialIndexLOD !== null) {
             this._loader.log(`deferred`);
-            const previousIndexLOD = this._nodeIndexLOD - 1;
-            this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
-            return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
+            const previousIndexLOD = this._materialIndexLOD - 1;
+            this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
+            return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
                 return this._loader.loadUriAsync(context, property, uri);
             });
         }
@@ -288,36 +282,50 @@ export class MSFT_lod implements IGLTFLoaderExtension {
                 throw new Error(`${context}: Uri is missing or the binary glTF is missing its binary chunk`);
             }
 
-            // Non-LOD buffers will be bucketed into the first LOD.
-            const indexLOD = this._indexLOD || 0;
+            const loadAsync = (bufferLODs: Array<IBufferInfo>, indexLOD: number) => {
+                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`);
 
-            const start = byteOffset;
-            const end = start + byteLength - 1;
-            let bufferLOD = this._bufferLODs[indexLOD];
-            if (bufferLOD) {
-                bufferLOD.start = Math.min(bufferLOD.start, start);
-                bufferLOD.end = Math.max(bufferLOD.end, end);
+            if (this._nodeIndexLOD !== null) {
+                return loadAsync(this._nodeBufferLODs, this._nodeIndexLOD);
+            }
+            else if (this._materialIndexLOD !== null) {
+                return loadAsync(this._materialBufferLODs, this._materialIndexLOD);
             }
             else {
-                bufferLOD = { start: start, end: end, loaded: new Deferred() };
-                this._bufferLODs[indexLOD] = bufferLOD;
+                return loadAsync(this._bufferLODs, 0);
             }
-
-            return bufferLOD.loaded.promise.then((data) => {
-                return new Uint8Array(data.buffer, data.byteOffset + byteOffset - bufferLOD.start, byteLength);
-            });
         }
 
         return null;
     }
 
-    private _loadBufferLOD(indexLOD: number): void {
-        const bufferLOD = this._bufferLODs[indexLOD];
-        this._loader.bin!.readAsync(bufferLOD.start, bufferLOD.end - bufferLOD.start + 1).then((data) => {
-            bufferLOD.loaded.resolve(data);
-        }, (error) => {
-            bufferLOD.loaded.reject(error);
-        });
+    private _loadBufferLOD(bufferLODs: Array<IBufferInfo>, indexLOD: number): void {
+        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);
+            });
+        }
     }
 
     /**

+ 16 - 77
loaders/src/glTF/2.0/glTFLoader.ts

@@ -3,7 +3,6 @@ import { Deferred } from "babylonjs/Misc/deferred";
 import { Quaternion, Vector3, Matrix } from "babylonjs/Maths/math.vector";
 import { Color3 } from 'babylonjs/Maths/math.color';
 import { Tools } from "babylonjs/Misc/tools";
-import { IFileRequest } from "babylonjs/Misc/fileRequest";
 import { Camera } from "babylonjs/Cameras/camera";
 import { FreeCamera } from "babylonjs/Cameras/freeCamera";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
@@ -22,7 +21,7 @@ import { InstancedMesh } from "babylonjs/Meshes/instancedMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { MorphTarget } from "babylonjs/Morph/morphTarget";
 import { MorphTargetManager } from "babylonjs/Morph/morphTargetManager";
-import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
+import { ISceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { Scene } from "babylonjs/scene";
 import { IProperty, AccessorType, CameraType, AnimationChannelTargetPath, AnimationSamplerInterpolation, AccessorComponentType, MaterialAlphaMode, TextureMinFilter, TextureWrapMode, TextureMagFilter, MeshPrimitiveMode } from "babylonjs-gltf2interface";
 import { _IAnimationSamplerData, IGLTF, ISampler, INode, IScene, IMesh, IAccessor, ISkin, ICamera, IAnimation, IAnimationChannel, IAnimationSampler, IBuffer, IBufferView, IMaterialPbrMetallicRoughness, IMaterial, ITextureInfo, ITexture, IImage, IMeshPrimitive, IArrayItem as IArrItem, _ISamplerData } from "./glTFLoaderInterfaces";
@@ -45,12 +44,6 @@ interface TypedArrayConstructor {
     new(buffer: ArrayBufferLike, byteOffset: number, length?: number): TypedArrayLike;
 }
 
-interface IFileRequestInfo extends IFileRequest {
-    _lengthComputable?: boolean;
-    _loaded?: number;
-    _total?: number;
-}
-
 interface ILoaderProperty extends IProperty {
     _activeLoaderExtensionFunctions: {
         [id: string]: boolean
@@ -121,8 +114,6 @@ export class GLTFLoader implements IGLTFLoader {
     private _babylonScene: Scene;
     private _rootBabylonMesh: Mesh;
     private _defaultBabylonMaterialData: { [drawMode: number]: Material } = {};
-    private _progressCallback?: (event: SceneLoaderProgressEvent) => void;
-    private _requests = new Array<IFileRequestInfo>();
 
     private static _RegisteredExtensions: { [name: string]: IRegisteredExtension } = {};
 
@@ -215,12 +206,6 @@ export class GLTFLoader implements IGLTFLoader {
 
         this._disposed = true;
 
-        for (const request of this._requests) {
-            request.abort();
-        }
-
-        this._requests.length = 0;
-
         this._completePromises.length = 0;
 
         for (const name in this._extensions) {
@@ -232,18 +217,16 @@ export class GLTFLoader implements IGLTFLoader {
         delete this._gltf;
         delete this._babylonScene;
         delete this._rootBabylonMesh;
-        delete this._progressCallback;
 
-        this._parent._clear();
+        this._parent.dispose();
     }
 
     /** @hidden */
-    public importMeshAsync(meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<IImportMeshAsyncOutput> {
+    public importMeshAsync(meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<IImportMeshAsyncOutput> {
         return Promise.resolve().then(() => {
             this._babylonScene = scene;
             this._rootUrl = rootUrl;
             this._fileName = fileName || "scene";
-            this._progressCallback = onProgress;
             this._forAssetContainer = forAssetContainer;
             this._loadData(data);
 
@@ -284,12 +267,11 @@ export class GLTFLoader implements IGLTFLoader {
     }
 
     /** @hidden */
-    public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
+    public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
         return Promise.resolve().then(() => {
             this._babylonScene = scene;
             this._rootUrl = rootUrl;
             this._fileName = fileName || "scene";
-            this._progressCallback = onProgress;
             this._loadData(data);
             return this._loadAsync(null, () => undefined);
         });
@@ -341,8 +323,8 @@ export class GLTFLoader implements IGLTFLoader {
                     this._rootBabylonMesh.setEnabled(true);
                 }
 
-                this._setState(GLTFLoaderState.READY);
                 this._extensionsOnReady();
+                this._setState(GLTFLoaderState.READY);
 
                 this._startAnimations();
 
@@ -1199,6 +1181,8 @@ export class GLTFLoader implements IGLTFLoader {
         this._parent.onCameraLoadedObservable.notifyObservers(babylonCamera);
         assign(babylonCamera);
 
+        this.logClose();
+
         return Promise.all(promises).then(() => {
             return babylonCamera;
         });
@@ -2039,64 +2023,19 @@ export class GLTFLoader implements IGLTFLoader {
         this.log(`Loading ${uri}`);
 
         return this._parent.preprocessUrlAsync(this._rootUrl + uri).then((url) => {
-            return new Promise<ArrayBufferView>((resolve, reject) => {
-                if (!this._disposed) {
-                    const request = Tools.LoadFile(url, (fileData) => {
-                        if (!this._disposed) {
-                            const data = new Uint8Array(fileData as ArrayBuffer);
-                            this.log(`Loaded ${uri} (${data.length} bytes)`);
-                            resolve(data);
-                        }
-                    }, (event) => {
-                        if (!this._disposed) {
-                            if (request) {
-                                request._lengthComputable = event.lengthComputable;
-                                request._loaded = event.loaded;
-                                request._total = event.total;
-                            }
-
-                            if (this._state === GLTFLoaderState.LOADING) {
-                                try {
-                                    this._onProgress();
-                                }
-                                catch (e) {
-                                    reject(e);
-                                }
-                            }
-                        }
-                    }, this._babylonScene.offlineProvider, true, (request, exception) => {
-                        if (!this._disposed) {
-                            reject(new LoadFileError(`${context}: Failed to load '${uri}'${request ? ": " + request.status + " " + request.statusText : ""}`, request));
-                        }
-                    }) as IFileRequestInfo;
-
-                    this._requests.push(request);
-                }
+            return new Promise((resolve, reject) => {
+                this._parent._loadFile(url, this._babylonScene, (data) => {
+                    if (!this._disposed) {
+                        this.log(`Loaded ${uri} (${(data as ArrayBuffer).byteLength} bytes)`);
+                        resolve(new Uint8Array(data as ArrayBuffer));
+                    }
+                }, true, (request) => {
+                    reject(new LoadFileError(`${context}: Failed to load '${uri}'${request ? ": " + request.status + " " + request.statusText : ""}`, request));
+                });
             });
         });
     }
 
-    private _onProgress(): void {
-        if (!this._progressCallback) {
-            return;
-        }
-
-        let lengthComputable = true;
-        let loaded = 0;
-        let total = 0;
-        for (let request of this._requests) {
-            if (request._lengthComputable === undefined || request._loaded === undefined || request._total === undefined) {
-                return;
-            }
-
-            lengthComputable = lengthComputable && request._lengthComputable;
-            loaded += request._loaded;
-            total += request._total;
-        }
-
-        this._progressCallback(new SceneLoaderProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
-    }
-
     /**
      * Adds a JSON pointer to the metadata of the Babylon object at `<object>.metadata.gltf.pointers`.
      * @param babylonObject the Babylon object with metadata

+ 95 - 33
loaders/src/glTF/glTFFileLoader.ts

@@ -9,7 +9,7 @@ import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { Material } from "babylonjs/Materials/material";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
-import { SceneLoader, ISceneLoaderPluginFactory, ISceneLoaderPlugin, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginExtensions } from "babylonjs/Loading/sceneLoader";
+import { SceneLoader, ISceneLoaderPluginFactory, ISceneLoaderPlugin, ISceneLoaderPluginAsync, ISceneLoaderProgressEvent, ISceneLoaderPluginExtensions } from "babylonjs/Loading/sceneLoader";
 import { AssetContainer } from "babylonjs/assetContainer";
 import { Scene, IDisposable } from "babylonjs/scene";
 import { WebRequest } from "babylonjs/Misc/webRequest";
@@ -19,6 +19,13 @@ import { DataReader, IDataBuffer } from 'babylonjs/Misc/dataReader';
 import { GLTFValidation } from './glTFValidation';
 import { Light } from 'babylonjs/Lights/light';
 import { TransformNode } from 'babylonjs/Meshes/transformNode';
+import { RequestFileError } from 'babylonjs/Misc/fileTools';
+
+interface IFileRequestInfo extends IFileRequest {
+    _lengthComputable?: boolean;
+    _loaded?: number;
+    _total?: number;
+}
 
 /**
  * Mode that determines the coordinate system to use.
@@ -124,8 +131,8 @@ export interface IImportMeshAsyncOutput {
 /** @hidden */
 export interface IGLTFLoader extends IDisposable {
     readonly state: Nullable<GLTFLoaderState>;
-    importMeshAsync: (meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<IImportMeshAsyncOutput>;
-    loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string) => Promise<void>;
+    importMeshAsync: (meshesNames: any, scene: Scene, forAssetContainer: boolean, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string) => Promise<IImportMeshAsyncOutput>;
+    loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string) => Promise<void>;
 }
 
 /**
@@ -441,6 +448,8 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
     }
 
     private _loader: Nullable<IGLTFLoader> = null;
+    private _progressCallback?: (event: ISceneLoaderProgressEvent) => void;
+    private _requests = new Array<IFileRequestInfo>();
 
     /**
      * Name of the loader ("gltf")
@@ -462,14 +471,14 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
             this._loader = null;
         }
 
-        this._clear();
+        for (const request of this._requests) {
+            request.abort();
+        }
 
-        this.onDisposeObservable.notifyObservers(undefined);
-        this.onDisposeObservable.clear();
-    }
+        this._requests.length = 0;
+
+        delete this._progressCallback;
 
-    /** @hidden */
-    public _clear(): void {
         this.preprocessUrlAsync = (url) => Promise.resolve(url);
 
         this.onMeshLoadedObservable.clear();
@@ -478,68 +487,68 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
         this.onCameraLoadedObservable.clear();
         this.onCompleteObservable.clear();
         this.onExtensionLoadedObservable.clear();
+
+        this.onDisposeObservable.notifyObservers(undefined);
+        this.onDisposeObservable.clear();
     }
 
     /** @hidden */
-    public requestFile(scene: Scene, url: string, onSuccess: (data: any, request?: WebRequest) => void, onProgress?: (ev: ProgressEvent) => void, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
+    public requestFile(scene: Scene, url: string, onSuccess: (data: any, request?: WebRequest) => void, onProgress?: (ev: ISceneLoaderProgressEvent) => void, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
+        this._progressCallback = onProgress;
+
         if (useArrayBuffer) {
             if (this.useRangeRequests) {
                 if (this.validate) {
                     Logger.Warn("glTF validation is not supported when range requests are enabled");
                 }
 
-                const fileRequests = new Array<IFileRequest>();
-                const aggregatedFileRequest: IFileRequest = {
-                    abort: () => fileRequests.forEach((fileRequest) => fileRequest.abort()),
+                const fileRequest: IFileRequest = {
+                    abort: () => { },
                     onCompleteObservable: new Observable<IFileRequest>()
                 };
 
                 const dataBuffer = {
                     readAsync: (byteOffset: number, byteLength: number) => {
                         return new Promise<ArrayBufferView>((resolve, reject) => {
-                            fileRequests.push(scene._requestFile(url, (data, webRequest) => {
-                                const contentRange = webRequest!.getResponseHeader("Content-Range");
-                                if (contentRange) {
-                                    dataBuffer.byteLength = Number(contentRange.split("/")[1]);
-                                }
+                            this._requestFile(url, scene, (data) => {
                                 resolve(new Uint8Array(data as ArrayBuffer));
-                            }, onProgress, true, true, (error) => {
+                            }, true, (error) => {
                                 reject(error);
                             }, (webRequest) => {
                                 webRequest.setRequestHeader("Range", `bytes=${byteOffset}-${byteOffset + byteLength - 1}`);
-                            }));
+                            });
                         });
                     },
                     byteLength: 0
                 };
 
                 this._unpackBinaryAsync(new DataReader(dataBuffer)).then((loaderData) => {
-                    aggregatedFileRequest.onCompleteObservable.notifyObservers(aggregatedFileRequest);
+                    fileRequest.onCompleteObservable.notifyObservers(fileRequest);
                     onSuccess(loaderData);
                 }, onError);
 
-                return aggregatedFileRequest;
+                return fileRequest;
             }
 
-            return scene._requestFile(url, (data, request) => {
+            return this._requestFile(url, scene, (data, request) => {
                 const arrayBuffer = data as ArrayBuffer;
                 this._unpackBinaryAsync(new DataReader({
                     readAsync: (byteOffset, byteLength) => Promise.resolve(new Uint8Array(arrayBuffer, byteOffset, byteLength)),
                     byteLength: arrayBuffer.byteLength
                 })).then((loaderData) => {
-                     onSuccess(loaderData, request);
+                    onSuccess(loaderData, request);
                 }, onError);
-            }, onProgress, true, true, onError);
+            }, true, onError);
         }
 
-        return scene._requestFile(url, (data, response) => {
+        return this._requestFile(url, scene, (data, request) => {
             this._validate(scene, data, Tools.GetFolderPath(url), Tools.GetFilename(url));
-            onSuccess({ json: this._parseJson(data as string) }, response);
-        }, onProgress, true, false, onError);
+            onSuccess({ json: this._parseJson(data as string) }, request);
+        }, useArrayBuffer, onError);
     }
 
     /** @hidden */
-    public readFile(scene: Scene, file: File, onSuccess: (data: any) => void, onProgress?: (ev: ProgressEvent) => any, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
+    public readFile(scene: Scene, file: File, onSuccess: (data: any) => void, onProgress?: (ev: ISceneLoaderProgressEvent) => any, useArrayBuffer?: boolean, onError?: (error: any) => void): IFileRequest {
         return scene._readFile(file, (data) => {
             this._validate(scene, data, "file:", file.name);
             if (useArrayBuffer) {
@@ -556,7 +565,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
     }
 
     /** @hidden */
-    public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
+    public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<{ meshes: AbstractMesh[], particleSystems: IParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
         return Promise.resolve().then(() => {
             this.onParsedObservable.notifyObservers(data);
             this.onParsedObservable.clear();
@@ -568,7 +577,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
     }
 
     /** @hidden */
-    public loadAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
+    public loadAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<void> {
         return Promise.resolve().then(() => {
             this.onParsedObservable.notifyObservers(data);
             this.onParsedObservable.clear();
@@ -580,7 +589,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
     }
 
     /** @hidden */
-    public loadAssetContainerAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
+    public loadAssetContainerAsync(scene: Scene, data: any, rootUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void, fileName?: string): Promise<AssetContainer> {
         return Promise.resolve().then(() => {
             this.onParsedObservable.notifyObservers(data);
             this.onParsedObservable.clear();
@@ -664,6 +673,59 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
         });
     }
 
+    /** @hidden */
+    public _loadFile(url: string, scene: Scene, onSuccess: (data: string | ArrayBuffer) => void, useArrayBuffer?: boolean, onError?: (request?: WebRequest) => void): IFileRequest {
+        const request = scene._loadFile(url, onSuccess, (event) => {
+            this._onProgress(event, request);
+        }, undefined, useArrayBuffer, onError) as IFileRequestInfo;
+        request.onCompleteObservable.add((request) => {
+            this._requests.splice(this._requests.indexOf(request), 1);
+        });
+        this._requests.push(request);
+        return request;
+    }
+
+    /** @hidden */
+    public _requestFile(url: string, scene: Scene, onSuccess: (data: string | ArrayBuffer, request?: WebRequest) => void, useArrayBuffer?: boolean, onError?: (error: RequestFileError) => void, onOpened?: (request: WebRequest) => void): IFileRequest {
+        const request = scene._requestFile(url, onSuccess, (event) => {
+            this._onProgress(event, request);
+        }, undefined, useArrayBuffer, onError, onOpened) as IFileRequestInfo;
+        request.onCompleteObservable.add((request) => {
+            this._requests.splice(this._requests.indexOf(request), 1);
+        });
+        this._requests.push(request);
+        return request;
+    }
+
+    private _onProgress(event: ProgressEvent, request: IFileRequestInfo): void {
+        if (!this._progressCallback) {
+            return;
+        }
+
+        request._lengthComputable = event.lengthComputable;
+        request._loaded = event.loaded;
+        request._total = event.total;
+
+        let lengthComputable = true;
+        let loaded = 0;
+        let total = 0;
+        for (let request of this._requests) {
+            if (request._lengthComputable === undefined || request._loaded === undefined || request._total === undefined) {
+                return;
+            }
+
+            lengthComputable = lengthComputable && request._lengthComputable;
+            loaded += request._loaded;
+            total += request._total;
+        }
+
+        this._progressCallback({
+            lengthComputable: lengthComputable,
+            loaded: loaded,
+            total: lengthComputable ? total : 0
+        });
+    }
+
     private _validate(scene: Scene, data: string | ArrayBuffer, rootUrl = "", fileName = ""): void {
         if (!this.validate) {
             return;
@@ -748,7 +810,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
             }
 
             const length = dataReader.readUint32();
-            if (dataReader.buffer.byteLength != 0 && length !== dataReader.buffer.byteLength) {
+            if (dataReader.buffer.byteLength !== 0 && length !== dataReader.buffer.byteLength) {
                 throw new Error(`Length in header does not match actual data length: ${length} != ${dataReader.buffer.byteLength}`);
             }
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 34 - 47
src/Loading/sceneLoader.ts


+ 3 - 3
src/Misc/filesInput.ts

@@ -1,6 +1,6 @@
 import { Engine } from "../Engines/engine";
 import { Scene } from "../scene";
-import { SceneLoaderProgressEvent, SceneLoader } from "../Loading/sceneLoader";
+import { ISceneLoaderProgressEvent, SceneLoader } from "../Loading/sceneLoader";
 import { Logger } from "../Misc/logger";
 import { FilesInputStore } from "./filesInputStore";
 
@@ -23,7 +23,7 @@ export class FilesInput {
     private _engine: Engine;
     private _currentScene: Scene;
     private _sceneLoadedCallback: (sceneFile: File, scene: Scene) => void;
-    private _progressCallback: (progress: SceneLoaderProgressEvent) => void;
+    private _progressCallback: (progress: ISceneLoaderProgressEvent) => void;
     private _additionalRenderLoopLogicCallback: () => void;
     private _textureLoadingCallback: (remaining: number) => void;
     private _startingProcessingFilesCallback: (files?: File[]) => void;
@@ -46,7 +46,7 @@ export class FilesInput {
      * @param onReloadCallback callback called when a reload is requested
      * @param errorCallback callback call if an error occurs
      */
-    constructor(engine: Engine, scene: Scene, sceneLoadedCallback: (sceneFile: File, scene: Scene) => void, progressCallback: (progress: SceneLoaderProgressEvent) => void, additionalRenderLoopLogicCallback: () => void,
+    constructor(engine: Engine, scene: Scene, sceneLoadedCallback: (sceneFile: File, scene: Scene) => void, progressCallback: (progress: ISceneLoaderProgressEvent) => void, additionalRenderLoopLogicCallback: () => void,
         textureLoadingCallback: (remaining: number) => void, startingProcessingFilesCallback: (files?: File[]) => void, onReloadCallback: (sceneFile: File) => void, errorCallback: (sceneFile: File, scene: Scene, message: string) => void) {
         this._engine = engine;
         this._currentScene = scene;

+ 13 - 4
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -427,13 +427,22 @@ describe('Babylon Scene Loader', function() {
 
             BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.useRangeRequests = true;
-                promises.push(loader.whenCompleteAsync());
+                loader.onExtensionLoadedObservable.add((extension) => {
+                    if (extension instanceof BABYLON.GLTF2.Loader.Extensions.MSFT_lod) {
+                        extension.onMaterialLODsLoadedObservable.add((indexLOD) => {
+                            expect(setRequestHeaderCalls, "setRequestHeaderCalls").to.have.ordered.members(expectedSetRequestHeaderCalls.slice(0, 3 + indexLOD));
+                        });
+                    }
+                });
+                promises.push(loader.whenCompleteAsync().then(() => {
+                    expect(setRequestHeaderCalls, "setRequestHeaderCalls").to.have.ordered.members(expectedSetRequestHeaderCalls);
+                    setRequestHeaderStub.restore();
+                    getResponseHeaderStub.restore();
+                }));
             });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/", "LevelOfDetail.glb", scene).then(() => {
-                expect(setRequestHeaderCalls, "setRequestHeaderCalls").to.have.ordered.members(expectedSetRequestHeaderCalls);
-                setRequestHeaderStub.restore();
-                getResponseHeaderStub.restore();
+                expect(setRequestHeaderCalls, "setRequestHeaderCalls").to.have.ordered.members(expectedSetRequestHeaderCalls.slice(0, 3));
             }));
 
             return Promise.all(promises);