Browse Source

Merge pull request #4326 from bghgary/lod-events

Add events for when LODs are loaded to MSFT_lod glTF loader extension
David Catuhe 7 years ago
parent
commit
f79acfcece

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

@@ -22,6 +22,7 @@
 ### glTF Loader
 
 - Added support for KHR_texture_transform ([bghgary](http://www.github.com/bghgary))
+- Added `onNodeLODsLoadedObservable` and `onMaterialLODsLoadedObservable` to MSFT_lod loader extension ([bghgary](http://www.github.com/bghgary))
 
 ### Viewer
 

+ 52 - 0
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -18,11 +18,57 @@ module BABYLON.GLTF2.Extensions {
          */
         public maxLODsToLoad = Number.MAX_VALUE;
 
+        /**
+         * Observable raised when all node LODs of one level are loaded.
+         * The event data is the index of the loaded LOD starting from zero.
+         * Dispose the loader to cancel the loading of the next level of LODs.
+         */
+        public onNodeLODsLoadedObservable = new Observable<number>();
+
+        /**
+         * Observable raised when all material LODs of one level are loaded.
+         * The event data is the index of the loaded LOD starting from zero.
+         * Dispose the loader to cancel the loading of the next level of LODs.
+         */
+        public onMaterialLODsLoadedObservable = new Observable<number>();
+
         private _loadingNodeLOD: Nullable<_ILoaderNode> = null;
         private _loadNodeSignals: { [nodeIndex: number]: Deferred<void> } = {};
+        private _loadNodePromises = new Array<Array<Promise<void>>>();
 
         private _loadingMaterialLOD: Nullable<_ILoaderMaterial> = null;
         private _loadMaterialSignals: { [materialIndex: number]: Deferred<void> } = {};
+        private _loadMaterialPromises = new Array<Array<Promise<void>>>();
+
+        constructor(loader: GLTFLoader) {
+            super(loader);
+
+            this._loader._onReadyObservable.addOnce(() => {
+                for (let indexLOD = 0; indexLOD < this._loadNodePromises.length; indexLOD++) {
+                    Promise.all(this._loadNodePromises[indexLOD]).then(() => {
+                        this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
+                    });
+                }
+
+                for (let indexLOD = 0; indexLOD < this._loadMaterialPromises.length; indexLOD++) {
+                    Promise.all(this._loadMaterialPromises[indexLOD]).then(() => {
+                        this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
+                    });
+                }
+            });
+        }
+
+        public dispose() {
+            super.dispose();
+
+            this._loadingNodeLOD = null;
+            this._loadNodeSignals = {};
+            this._loadingMaterialLOD = null;
+            this._loadMaterialSignals = {};
+
+            this.onMaterialLODsLoadedObservable.clear();
+            this.onNodeLODsLoadedObservable.clear();
+        }
 
         protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IMSFTLOD>(context, node, (extensionContext, extension) => {
@@ -66,6 +112,9 @@ module BABYLON.GLTF2.Extensions {
                         this._loader._completePromises.push(promise);
                         this._loadingNodeLOD = null;
                     }
+
+                    this._loadNodePromises[indexLOD] = this._loadNodePromises[indexLOD] || [];
+                    this._loadNodePromises[indexLOD].push(promise);
                 }
 
                 return firstPromise!;
@@ -121,6 +170,9 @@ module BABYLON.GLTF2.Extensions {
                         this._loader._completePromises.push(promise);
                         this._loadingMaterialLOD = null;
                     }
+
+                    this._loadMaterialPromises[indexLOD] = this._loadMaterialPromises[indexLOD] || [];
+                    this._loadMaterialPromises[indexLOD].push(promise);
                 }
 
                 return firstPromise!;

+ 5 - 0
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -23,6 +23,9 @@ module BABYLON.GLTF2 {
         /** @hidden */
         public _completePromises = new Array<Promise<void>>();
 
+        /** @hidden */
+        public _onReadyObservable = new Observable<IGLTFLoader>();
+
         private _disposed = false;
         private _state: Nullable<GLTFLoaderState> = null;
         private _extensions: { [name: string]: GLTFLoaderExtension } = {};
@@ -238,6 +241,7 @@ module BABYLON.GLTF2 {
 
                 const resultPromise = Promise.all(promises).then(() => {
                     this._state = GLTFLoaderState.READY;
+                    this._onReadyObservable.notifyObservers(this);
                     this._startAnimations();
                 });
 
@@ -1753,6 +1757,7 @@ module BABYLON.GLTF2 {
             delete this._gltf;
             delete this._babylonScene;
             this._completePromises.length = 0;
+            this._onReadyObservable.clear();
 
             for (const name in this._extensions) {
                 this._extensions[name].dispose();

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

@@ -406,9 +406,9 @@ module BABYLON {
          */
         public whenCompleteAsync(): Promise<void> {
             return new Promise(resolve => {
-                this.onCompleteObservable.add(() => {
+                this.onCompleteObservable.addOnce(() => {
                     resolve();
-                }, undefined, undefined, undefined, true);
+                });
             });
         }
 

+ 9 - 0
src/Tools/babylon.observable.ts

@@ -196,6 +196,15 @@
         }
 
         /**
+         * Create a new Observer with the specified callback and unregisters after the next notification
+         * @param callback the callback that will be executed for that Observer
+         * @returns the new observer created for the callback
+         */
+        public addOnce(callback: (eventData: T, eventState: EventState) => void): Nullable<Observer<T>> {
+            return this.add(callback, undefined, undefined, undefined, true);
+        }
+
+        /**
          * Remove an Observer from the Observable object
          * @param observer the instance of the Observer to remove
          * @returns false if it doesn't belong to this Observable

+ 67 - 14
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -75,7 +75,7 @@ describe('Babylon Scene Loader', function () {
 
             const promises = new Array<Promise<void>>();
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.onParsed = data => {
                     parsedCount++;
                 };
@@ -94,7 +94,7 @@ describe('Babylon Scene Loader', function () {
                 promises.push(loader.whenCompleteAsync().then(() => {
                     expect(ready, "ready").to.be.true;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             const scene = new BABYLON.Scene(subject);
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
@@ -125,7 +125,7 @@ describe('Babylon Scene Loader', function () {
 
             const promises = new Array<Promise<void>>();
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.onDispose = () => {
                     disposed = true;
                 };
@@ -135,7 +135,7 @@ describe('Babylon Scene Loader', function () {
                     expect(ready, "ready").to.be.false;
                     expect(disposed, "disposed").to.be.true;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             const scene = new BABYLON.Scene(subject);
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox2.gltf", scene).then(() => {
@@ -183,7 +183,7 @@ describe('Babylon Scene Loader', function () {
                 }
             });
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.compileMaterials = true;
 
                 promises.push(loader.whenCompleteAsync().then(() => {
@@ -191,7 +191,7 @@ describe('Babylon Scene Loader', function () {
                     createShaderProgramSpy.restore();
                     expect(called, "createShaderProgramCalled").to.be.false;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("http://models.babylonjs.com/Tests/CompileMaterials/", "Test.gltf", scene).then(() => {
                 createShaderProgramSpy = sinon.spy(subject, "createShaderProgram");
@@ -213,7 +213,7 @@ describe('Babylon Scene Loader', function () {
                 }
             });
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.compileMaterials = true;
 
                 promises.push(loader.whenCompleteAsync().then(() => {
@@ -221,7 +221,7 @@ describe('Babylon Scene Loader', function () {
                     createShaderProgramSpy.restore();
                     expect(called, "createShaderProgramCalled").to.be.false;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BrainStem/", "BrainStem.gltf", scene).then(() => {
                 createShaderProgramSpy = sinon.spy(subject, "createShaderProgram");
@@ -282,7 +282,7 @@ describe('Babylon Scene Loader', function () {
                 }
             });
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 loader.compileMaterials = true;
 
                 promises.push(loader.whenCompleteAsync().then(() => {
@@ -302,7 +302,7 @@ describe('Babylon Scene Loader', function () {
                     expect(materials[0].isReady(meshes[0]), "Material of LOD 0 is ready for node 0").to.be.true;
                     expect(materials[0].isReady(meshes[1]), "Material of LOD 0 is ready for node 1").to.be.true;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("http://models.babylonjs.com/Tests/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
                 const meshes = [
@@ -331,6 +331,59 @@ describe('Babylon Scene Loader', function () {
             return Promise.all(promises);
         });
 
+        it('Load TwoQuads with LODs and onMaterialLODsLoadedObservable', () => {
+            const scene = new BABYLON.Scene(subject);
+            const promises = new Array<Promise<void>>();
+
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
+                loader.onExtensionLoadedObservable.addOnce(extension => {
+                    if (extension instanceof BABYLON.GLTF2.Extensions.MSFT_lod) {
+                        extension.onMaterialLODsLoadedObservable.add(indexLOD => {
+                            const expectedMaterialName = `LOD${2 - indexLOD}`;
+                            expect(scene.getMeshByName("node0").material.name, "Material for node 0").to.equal(expectedMaterialName);
+                            expect(scene.getMeshByName("node1").material.name, "Material for node 1").to.equal(expectedMaterialName);
+                        });
+                    }
+                });
+
+                promises.push(loader.whenCompleteAsync());
+            });
+
+            promises.push(BABYLON.SceneLoader.AppendAsync("http://models.babylonjs.com/Tests/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
+                // do nothing
+            }));
+
+            return Promise.all(promises);
+        });
+
+        it('Load TwoQuads with LODs and dispose when onMaterialLODsLoadedObservable', () => {
+            const scene = new BABYLON.Scene(subject);
+            const promises = new Array<Promise<void>>();
+
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
+                loader.onExtensionLoadedObservable.addOnce(extension => {
+                    if (extension instanceof BABYLON.GLTF2.Extensions.MSFT_lod) {
+                        extension.onMaterialLODsLoadedObservable.add(indexLOD => {
+                            expect(indexLOD, "indexLOD").to.equal(0);
+                            loader.dispose();
+                        });
+                    }
+                });
+
+                promises.push(new Promise(resolve => {
+                    loader.onDisposeObservable.addOnce(() => {
+                        resolve();
+                    });
+                }));
+            });
+
+            promises.push(BABYLON.SceneLoader.AppendAsync("http://models.babylonjs.com/Tests/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
+                // do nothing
+            }));
+
+            return Promise.all(promises);
+        });
+
         it('Load MultiPrimitive', () => {
             const scene = new BABYLON.Scene(subject);
             return BABYLON.SceneLoader.ImportMeshAsync(null, "http://models.babylonjs.com/Tests/MultiPrimitive/", "MultiPrimitive.gltf", scene).then(result => {
@@ -372,7 +425,7 @@ describe('Babylon Scene Loader', function () {
 
             const promises = new Array<Promise<any>>();
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 var specularOverAlpha = false;
                 var radianceOverAlpha = false;
 
@@ -385,7 +438,7 @@ describe('Babylon Scene Loader', function () {
                     expect(specularOverAlpha, "specularOverAlpha").to.be.false;
                     expect(radianceOverAlpha, "radianceOverAlpha").to.be.false;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene));
             return Promise.all(promises);
@@ -396,7 +449,7 @@ describe('Babylon Scene Loader', function () {
 
             const promises = new Array<Promise<any>>();
 
-            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+            BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce((loader: BABYLON.GLTFFileLoader) => {
                 var specularOverAlpha = true;
                 var radianceOverAlpha = true;
 
@@ -409,7 +462,7 @@ describe('Babylon Scene Loader', function () {
                     expect(specularOverAlpha, "specularOverAlpha").to.be.true;
                     expect(radianceOverAlpha, "radianceOverAlpha").to.be.true;
                 }));
-            }, undefined, undefined, undefined, true);
+            });
 
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene));
             return Promise.all(promises);