Pārlūkot izejas kodu

Bunch of glTF loader bug fixes

Gary Hsu 7 gadi atpakaļ
vecāks
revīzija
29f68a2cab

+ 2 - 0
Playground/scenes/TwoQuads/TwoQuads.gltf

@@ -121,6 +121,7 @@
   ],
   "nodes": [
     {
+      "name": "node0",
       "mesh": 0,
       "translation": [
         -0.55,
@@ -129,6 +130,7 @@
       ]
     },
     {
+      "name": "node1",
       "mesh": 0,
       "translation": [
         0.55,

+ 1 - 0
loaders/src/glTF/1.0/babylon.glTFLoader.ts

@@ -1571,6 +1571,7 @@ module BABYLON.GLTF1 {
         public onMeshLoadedObservable = new Observable<AbstractMesh>();
         public onTextureLoadedObservable = new Observable<BaseTexture>();
         public onMaterialLoadedObservable = new Observable<Material>();
+        public onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
         public onCompleteObservable = new Observable<IGLTFLoader>();
         public onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
 

+ 16 - 19
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -16,33 +16,30 @@ module BABYLON.GLTF2.Extensions {
     export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         public readonly name = NAME;
 
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension) => {
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes.push(babylonMesh);
 
-                if (material._loaded) {
-                    babylonMesh.material = material._babylonMaterial!;
-                    return material._loaded;
-                }
-
-                const promises = new Array<Promise<void>>();
+                if (!material._loaded) {
+                    const promises = new Array<Promise<void>>();
 
-                const babylonMaterial = this._loader._createMaterial(material);
-                material._babylonMaterial = babylonMaterial;
+                    const babylonMaterial = this._loader._createMaterial(material);
+                    material._babylonMaterial = babylonMaterial;
 
-                promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material));
-                promises.push(this._loadSpecularGlossinessPropertiesAsync(this._loader, context, material, extension));
+                    promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material));
+                    promises.push(this._loadSpecularGlossinessPropertiesAsync(context, material, extension));
 
-                this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-
-                babylonMesh.material = babylonMaterial;
+                    this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                    material._loaded = Promise.all(promises).then(() => {});
+                }
 
-                return (material._loaded = Promise.all(promises).then(() => {}));
+                assign(material._babylonMaterial!);
+                return material._loaded;
             });
         }
 
-        private _loadSpecularGlossinessPropertiesAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): Promise<void> {
+        private _loadSpecularGlossinessPropertiesAsync(context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): Promise<void> {
             const promises = new Array<Promise<void>>();
 
             const babylonMaterial = material._babylonMaterial as PBRMaterial;
@@ -59,13 +56,13 @@ module BABYLON.GLTF2.Extensions {
             babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
 
             if (properties.diffuseTexture) {
-                promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, texture => {
+                promises.push(this._loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, texture => {
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
 
             if (properties.specularGlossinessTexture) {
-                promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, texture => {
+                promises.push(this._loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, texture => {
                     babylonMaterial.reflectivityTexture = texture;
                 }));
 
@@ -73,7 +70,7 @@ module BABYLON.GLTF2.Extensions {
                 babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
             }
 
-            loader._loadMaterialAlphaProperties(context, material);
+            this._loader._loadMaterialAlphaProperties(context, material);
 
             return Promise.all(promises).then(() => {});
         }

+ 6 - 2
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -68,7 +68,7 @@ module BABYLON.GLTF2.Extensions {
             });
         }
 
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
             // Don't load material LODs if already loading a node LOD.
             if (this._loadingNodeLOD) {
                 return null;
@@ -89,7 +89,11 @@ module BABYLON.GLTF2.Extensions {
                         }
                     }
 
-                    const promise = this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(() => {
+                    const promise = this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh, indexLOD === 0 ? assign : () => {}).then(() => {
+                        if (indexLOD !== 0) {
+                            assign(materialLOD._babylonMaterial!);
+                        }
+
                         if (indexLOD !== materialLODs.length - 1) {
                             const materialIndex = materialLODs[indexLOD + 1]._index;
                             if (this._loadMaterialSignals[materialIndex]) {

+ 30 - 23
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -54,6 +54,7 @@ module BABYLON.GLTF2 {
         public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
         public readonly onMaterialLoadedObservable = new Observable<Material>();
+        public readonly onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
         public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
         public readonly onCompleteObservable = new Observable<IGLTFLoader>();
 
@@ -143,9 +144,13 @@ module BABYLON.GLTF2 {
                     promises.push(this._compileShadowGeneratorsAsync());
                 }
 
-                return Promise.all(promises).then(() => {
+                const resultPromise = Promise.all(promises).then(() => {
                     this._state = GLTFLoaderState.Ready;
                     this._startAnimations();
+                });
+
+                resultPromise.then(() => {
+                    this._rootBabylonMesh.setEnabled(true);
 
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
@@ -161,6 +166,8 @@ module BABYLON.GLTF2 {
                         }
                     });
                 });
+
+                return resultPromise;
             }).catch(error => {
                 Tools.Error("glTF Loader: " + error.message);
                 this._clear();
@@ -245,6 +252,8 @@ module BABYLON.GLTF2 {
 
         private _createRootNode(): ILoaderNode {
             this._rootBabylonMesh = new Mesh("__root__", this._babylonScene);
+            this._rootBabylonMesh.setEnabled(false);
+
             const rootNode = { _babylonMesh: this._rootBabylonMesh } as ILoaderNode;
             switch (this.coordinateSystemMode) {
                 case GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -436,7 +445,6 @@ module BABYLON.GLTF2 {
             const promises = new Array<Promise<void>>();
 
             const babylonMesh = new Mesh((mesh.name || node._babylonMesh!.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-            babylonMesh.setEnabled(false);
 
             node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
             node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
@@ -453,14 +461,14 @@ module BABYLON.GLTF2 {
             }
             else {
                 const material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
-                promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
+                promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh, babylonMaterial => {
+                    babylonMesh.material = babylonMaterial;
+                }));
             }
 
             this.onMeshLoadedObservable.notifyObservers(babylonMesh);
 
-            return Promise.all(promises).then(() => {
-                babylonMesh.setEnabled(true);
-            });
+            return Promise.all(promises).then(() => {});
         }
 
         private _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<VertexData> {
@@ -813,6 +821,8 @@ module BABYLON.GLTF2 {
                 promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
             }
 
+            this.onAnimationGroupLoadedObservable.notifyObservers(babylonAnimationGroup);
+
             return Promise.all(promises).then(() => {
                 babylonAnimationGroup.normalize();
             });
@@ -820,7 +830,7 @@ module BABYLON.GLTF2 {
 
         private _loadAnimationChannelAsync(context: string, animationContext: string, animation: ILoaderAnimation, channel: ILoaderAnimationChannel, babylonAnimationGroup: AnimationGroup): Promise<void> {
             const targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
-            if (!targetNode._babylonMesh) {
+            if (!targetNode._babylonMesh || targetNode.skin != undefined) {
                 return Promise.resolve();
             }
 
@@ -1177,8 +1187,8 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
         }
 
-        public _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh);
+        public _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Promise<void> {
+            const promise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh, assign);
             if (promise) {
                 return promise;
             }
@@ -1186,24 +1196,21 @@ module BABYLON.GLTF2 {
             material._babylonMeshes = material._babylonMeshes || [];
             material._babylonMeshes.push(babylonMesh);
 
-            if (material._loaded) {
-                babylonMesh.material = material._babylonMaterial!;
-                return material._loaded;
-            }
-
-            const promises = new Array<Promise<void>>();
-
-            const babylonMaterial = this._createMaterial(material);
-            material._babylonMaterial = babylonMaterial;
+            if (!material._loaded) {
+                const promises = new Array<Promise<void>>();
 
-            promises.push(this._loadMaterialBasePropertiesAsync(context, material));
-            promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
+                const babylonMaterial = this._createMaterial(material);
+                material._babylonMaterial = babylonMaterial;
 
-            this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                promises.push(this._loadMaterialBasePropertiesAsync(context, material));
+                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
 
-            babylonMesh.material = babylonMaterial;
+                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                material._loaded = Promise.all(promises).then(() => {});
+            }
 
-            return (material._loaded = Promise.all(promises).then(() => {}));
+            assign(material._babylonMaterial!);
+            return material._loaded;
         }
 
         public _createMaterial(material: ILoaderMaterial): PBRMaterial {

+ 3 - 3
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -27,7 +27,7 @@ module BABYLON.GLTF2 {
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> { return null; }
 
         /** Override this method to modify the default behavior for loading materials. */
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> { return null; }
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> { return null; }
 
         /** Override this method to modify the default behavior for loading uris. */
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> { return null; }
@@ -75,8 +75,8 @@ module BABYLON.GLTF2 {
         }
 
         /** Helper method called by the loader to allow extensions to override loading materials. */
-        public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, babylonMesh));
+        public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, babylonMesh, assign));
         }
 
         /** Helper method called by the loader to allow extensions to override loading uris. */

+ 28 - 2
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -63,6 +63,7 @@ module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -150,7 +151,7 @@ module BABYLON {
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
 
         private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
-        public set onTextureLoaded(callback: (Texture: BaseTexture) => void) {
+        public set onTextureLoaded(callback: (texture: BaseTexture) => void) {
             if (this._onTextureLoadedObserver) {
                 this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
             }
@@ -163,7 +164,7 @@ module BABYLON {
         public readonly onMaterialLoadedObservable = new Observable<Material>();
 
         private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
-        public set onMaterialLoaded(callback: (Material: Material) => void) {
+        public set onMaterialLoaded(callback: (material: Material) => void) {
             if (this._onMaterialLoadedObserver) {
                 this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
             }
@@ -171,6 +172,19 @@ module BABYLON {
         }
 
         /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the material.
+         */
+        public readonly onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
+
+        private _onAnimationGroupLoadedObserver: Nullable<Observer<AnimationGroup>>;
+        public set onAnimationGroupLoaded(callback: (animationGroup: AnimationGroup) => void) {
+            if (this._onAnimationGroupLoadedObserver) {
+                this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+            }
+            this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+        }
+
+        /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -213,6 +227,18 @@ module BABYLON {
         }
 
         /**
+         * Gets a promise that resolves when the asset to be completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        public whenCompleteAsync(): Promise<void> {
+            return new Promise(resolve => {
+                this.onCompleteObservable.add(() => {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        }
+
+        /**
          * The loader state or null if not active.
          */
         public get loaderState(): Nullable<GLTFLoaderState> {

+ 23 - 0
src/Tools/babylon.promise.ts

@@ -196,6 +196,29 @@ module BABYLON {
 
             return newPromise;
         }
+
+        public static race<T>(promises: InternalPromise<T>[]): InternalPromise<T> {
+            let newPromise: Nullable<InternalPromise<T>> = new InternalPromise();
+
+            if (promises.length) {
+                for (const promise of promises) {
+                    promise.then((value?: Nullable<T>) => {
+                        if (newPromise) {
+                            newPromise._resolve(value);
+                            newPromise = null;
+                        }
+                        return null;
+                    }, (reason: any) => {
+                        if (newPromise) {
+                            newPromise._reject(reason);
+                            newPromise = null;
+                        }
+                    });
+                }
+            }
+
+            return newPromise;
+        }
     }
 
     /**

+ 74 - 52
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -69,7 +69,8 @@ describe('Babylon Scene Loader', function () {
             let textureCounts: { [name: string]: number } = {};
             let ready = false;
 
-            const deferred = new BABYLON.Deferred();
+            const promises = new Array<Promise<void>>();
+
             BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
                 loader.onParsed = data => {
                     parsedCount++;
@@ -86,19 +87,13 @@ describe('Babylon Scene Loader', function () {
                     textureCounts[texture.name]++;
                 };
 
-                loader.onComplete = () => {
-                    try {
-                        expect(ready, "ready").to.be.true;
-                        deferred.resolve();
-                    }
-                    catch (e) {
-                        deferred.reject(e);
-                    }
-                };
+                promises.push(loader.whenCompleteAsync().then(() => {
+                    expect(ready, "ready").to.be.true;
+                }));
             }, undefined, undefined, undefined, true);
 
             const scene = new BABYLON.Scene(subject);
-            const promise = BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
+            promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
                 ready = true;
 
                 expect(parsedCount, "parsedCount").to.equal(1);
@@ -115,70 +110,88 @@ describe('Babylon Scene Loader', function () {
                 for (const textureName in expectedTextureLoadCounts) {
                     expect(textureCounts, "textureCounts").to.have.property(textureName, expectedTextureLoadCounts[textureName]);
                 }
-            });
+            }));
 
-            return Promise.all([promise, deferred.promise]);
+            return Promise.all(promises);
         });
 
         it('Load BoomBox with dispose', () => {
             let ready = false;
             let disposed = false;
 
-            const deferred = new BABYLON.Deferred<void>();
+            const promises = new Array<Promise<void>>();
+
             BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
                 loader.onDispose = () => {
                     disposed = true;
                 };
 
-                BABYLON.Tools.DelayAsync(50).then(() => {
+                promises.push(BABYLON.Tools.DelayAsync(50).then(() => {
                     loader.dispose();
                     expect(ready, "ready").to.be.false;
                     expect(disposed, "disposed").to.be.true;
-                    deferred.resolve();
-                }).catch(error => {
-                    deferred.reject(error);
-                });
+                }));
             }, undefined, undefined, undefined, true);
 
             const scene = new BABYLON.Scene(subject);
-            BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox2.gltf", scene).then(() => {
+            promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox2.gltf", scene).then(() => {
                 ready = true;
-            }).catch(error => {
-                // Cannot rely on the typical error handling of promises since the AppendAsync is not
-                // supposed to complete because the loader is being disposed.
-                deferred.reject(error);
-            });
+            }));
 
-            return deferred.promise;
+            return Promise.race(promises);
         });
 
         it('Load BoomBox with compileMaterials', () => {
             let createShaderProgramSpy: sinon.SinonSpy;
 
-            const deferred = new BABYLON.Deferred();
+            const promises = new Array<Promise<void>>();
+
             BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
                 loader.compileMaterials = true;
 
-                loader.onComplete = () => {
+                promises.push(loader.whenCompleteAsync().then(() => {
                     try {
                         expect(createShaderProgramSpy.called, "createShaderProgramSpy.called").to.be.false;
-                        deferred.resolve();
-                    }
-                    catch (e) {
-                        deferred.reject(e);
                     }
                     finally {
                         createShaderProgramSpy.restore();
                     }
-                };
+                }));
             }, undefined, undefined, undefined, true);
 
             const scene = new BABYLON.Scene(subject);
-            const promise = BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
+            promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
                 createShaderProgramSpy = sinon.spy(subject, "createShaderProgram");
+            }));
+
+            promises.push(scene.whenReadyAsync());
+
+            return Promise.all(promises);
+        });
+
+        it('Load BoomBox with rootMesh.isEnabled check', () => {
+            const scene = new BABYLON.Scene(subject);
+            let rootMesh: BABYLON.AbstractMesh;
+
+            subject.runRenderLoop(() => {
+                if (!rootMesh) {
+                    for (const mesh of scene.meshes) {
+                        if (!mesh.parent) {
+                            rootMesh = mesh;
+                            break;
+                        }
+                    }
+                }
+
+                if (rootMesh) {
+                    expect(rootMesh.isEnabled(), "rootMesh.isEnabled").to.be.false;
+                }
             });
 
-            return Promise.all([promise, deferred.promise, scene.whenReadyAsync()]);
+            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(scene => {
+                expect(rootMesh.isEnabled(), "rootMesh.isEnabled").to.be.true;
+                subject.stopRenderLoop();
+            });
         });
 
         it('Load Alien', () => {
@@ -204,31 +217,36 @@ describe('Babylon Scene Loader', function () {
             });
         });
 
-        it('Load TwoQuads', () => {
+        it('Load TwoQuads with LODs', () => {
+            const scene = new BABYLON.Scene(subject);
+            const promises = new Array<Promise<void>>();
             const materials: { [name: string]: BABYLON.Material } = {};
 
-            const deferred = new BABYLON.Deferred();
+            subject.runRenderLoop(() => {
+                for (const mesh of scene.meshes) {
+                    if (mesh.material && mesh.isEnabled()) {
+                        expect(mesh.material.getActiveTextures().every(texture => texture.isReady()), "active mesh material textures are ready").to.be.true;
+                    }
+                }
+            });
+
             BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
                 loader.onMaterialLoaded = material => {
                     expect(materials[material.name], `materials["${material.name}"]`).to.be.undefined;
                     materials[material.name] = material;
                 };
 
-                loader.onComplete = () => {
-                    try {
-                        expect(materials["LOD0"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 0 ready").to.be.true;
-                        expect(materials["LOD1"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 1 ready").to.be.true;
-                        expect(materials["LOD2"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 2 ready").to.be.true;
-                        deferred.resolve();
-                    }
-                    catch (e) {
-                        deferred.reject(e);
-                    }
-                };
+                promises.push(loader.whenCompleteAsync().then(() => {
+                    expect(materials["LOD0"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 0 ready").to.be.true;
+                    expect(materials["LOD1"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 1 ready").to.be.true;
+                    expect(materials["LOD2"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 2 ready").to.be.true;
+
+                    expect(scene.getMeshByName("node0_0").material.name, "node 0 primitive 0 material").to.equal("LOD0");
+                    expect(scene.getMeshByName("node1_0").material.name, "node 1 primitive 0 material").to.equal("LOD0");
+                }));
             }, undefined, undefined, undefined, true);
 
-            const scene = new BABYLON.Scene(subject);
-            const promise = BABYLON.SceneLoader.AppendAsync("/Playground/scenes/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
+            promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
                 expect(Object.keys(materials), "materials").to.have.lengthOf(3);
 
                 expect(materials["LOD0"].getActiveTextures(), "material LOD 0 active textures").to.have.lengthOf(1);
@@ -238,11 +256,15 @@ describe('Babylon Scene Loader', function () {
                 expect(materials["LOD0"].getActiveTextures().some(texture => texture.isReady()), "Some textures of LOD 0 ready").to.be.false;
                 expect(materials["LOD1"].getActiveTextures().some(texture => texture.isReady()), "Some textures of LOD 1 ready").to.be.false;
                 expect(materials["LOD2"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 2 ready").to.be.true;
-            });
 
-            return Promise.all([promise, deferred.promise]);
+                expect(scene.getMeshByName("node0_0").material.name, "node 0 primitive 0 material").to.equal("LOD2");
+                expect(scene.getMeshByName("node1_0").material.name, "node 1 primitive 0 material").to.equal("LOD2");
+            }));
+
+            return Promise.all(promises);
         });
 
+        // TODO: test animation group callback
         // TODO: test material instancing
         // TODO: test ImportMesh with specific node name
         // TODO: test KHR_materials_pbrSpecularGlossiness