Kaynağa Gözat

Merge pull request #3684 from bghgary/loader-fixes

glTF Loader extension and compileMaterials fixes
David Catuhe 7 yıl önce
ebeveyn
işleme
f8368de944

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

@@ -1572,9 +1572,9 @@ module BABYLON.GLTF1 {
         public onTextureLoadedObservable = new Observable<BaseTexture>();
         public onMaterialLoadedObservable = new Observable<Material>();
         public onCompleteObservable = new Observable<IGLTFLoader>();
+        public onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
 
         public state: Nullable<GLTFLoaderState> = null;
-        public extensions: Nullable<IGLTFLoaderExtensions> = null;
 
         public dispose(): void {}
         // #endregion

+ 6 - 8
loaders/src/glTF/2.0/Extensions/KHR_lights.ts

@@ -31,10 +31,8 @@ module BABYLON.GLTF2.Extensions {
         lights: ILight[];
     }
 
-    export class KHRLights extends GLTFLoaderExtension {
-        protected get _name(): string {
-            return NAME;
-        }
+    export class KHR_lights extends GLTFLoaderExtension {
+        public readonly name = NAME;
 
         protected _loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>> { 
             return this._loadExtensionAsync<ILightReference>(context, scene, (context, extension) => {
@@ -94,14 +92,14 @@ module BABYLON.GLTF2.Extensions {
 
         private get _lights(): Array<ILight> {
             const extensions = this._loader._gltf.extensions;
-            if (!extensions || !extensions[this._name]) {
-                throw new Error("#/extensions: " + this._name + " not found");
+            if (!extensions || !extensions[this.name]) {
+                throw new Error("#/extensions: " + this.name + " not found");
             }
 
-            const extension = extensions[this._name] as ILights;
+            const extension = extensions[this.name] as ILights;
             return extension.lights;
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHRLights(loader));
+    GLTFLoader._Register(NAME, loader => new KHR_lights(loader));
 }

+ 3 - 5
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -13,10 +13,8 @@ module BABYLON.GLTF2.Extensions {
         specularGlossinessTexture: ITextureInfo;
     }
 
-    export class KHRMaterialsPbrSpecularGlossiness extends GLTFLoaderExtension {
-        protected get _name(): string {
-            return NAME;
-        }
+    export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
+        public readonly name = NAME;
 
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension) => {
@@ -81,5 +79,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHRMaterialsPbrSpecularGlossiness(loader));
+    GLTFLoader._Register(NAME, loader => new KHR_materials_pbrSpecularGlossiness(loader));
 }

+ 30 - 13
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -9,22 +9,25 @@ module BABYLON.GLTF2.Extensions {
         ids: number[];
     }
 
-    export class MSFTLOD extends GLTFLoaderExtension {
+    export class MSFT_lod extends GLTFLoaderExtension {
+        public readonly name = NAME;
+
+        /**
+         * Maximum number of LODs to load, starting from the lowest LOD.
+         */
+        public maxLODsToLoad = Number.MAX_VALUE;
+
         private _loadingNodeLOD: Nullable<ILoaderNode> = null;
         private _loadNodeSignals: { [nodeIndex: number]: Deferred<void> } = {};
 
         private _loadingMaterialLOD: Nullable<ILoaderMaterial> = null;
         private _loadMaterialSignals: { [materialIndex: number]: Deferred<void> } = {};
 
-        protected get _name() {
-            return NAME;
-        }
-
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IMSFTLOD>(context, node, (context, extension) => {
                 let firstPromise: Promise<void>;
 
-                const nodeLODs = MSFTLOD._GetLODs(context, node, this._loader._gltf.nodes, extension.ids);
+                const nodeLODs = this._getLODs(context, node, this._loader._gltf.nodes, extension.ids);
                 for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
                     const nodeLOD = nodeLODs[indexLOD];
 
@@ -60,10 +63,15 @@ module BABYLON.GLTF2.Extensions {
         }
 
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+            // Don't load material LODs if already loading a node LOD.
+            if (this._loadingNodeLOD) {
+                return null;
+            }
+
             return this._loadExtensionAsync<IMSFTLOD>(context, material, (context, extension) => {
                 let firstPromise: Promise<void>;
 
-                const materialLODs = MSFTLOD._GetLODs(context, material, this._loader._gltf.materials, extension.ids);
+                const materialLODs = this._getLODs(context, material, this._loader._gltf.materials, extension.ids);
                 for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
                     const materialLOD = materialLODs[indexLOD];
 
@@ -114,15 +122,24 @@ module BABYLON.GLTF2.Extensions {
         /**
          * Gets an array of LOD properties from lowest to highest.
          */
-        private static _GetLODs<T>(context: string, property: T, array: ArrayLike<T> | undefined, ids: number[]): T[] {
-            const properties = [property];
-            for (const id of ids) {
-                properties.push(GLTFLoader._GetProperty(context + "/ids/" + id, array, id));
+        private _getLODs<T>(context: string, property: T, array: ArrayLike<T> | undefined, ids: number[]): T[] {
+            if (this.maxLODsToLoad <= 0) {
+                throw new Error("maxLODsToLoad must be greater than zero");
+            }
+
+            const properties = new Array<T>();
+
+            for (let i = ids.length - 1; i >= 0; i--) {
+                properties.push(GLTFLoader._GetProperty(context + "/ids/" + ids[i], array, ids[i]));
+                if (properties.length === this.maxLODsToLoad) {
+                    return properties;
+                }
             }
 
-            return properties.reverse();
+            properties.push(property);
+            return properties;
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new MSFTLOD(loader));
+    GLTFLoader._Register(NAME, loader => new MSFT_lod(loader));
 }

+ 45 - 37
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -54,23 +54,13 @@ module BABYLON.GLTF2 {
         public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
         public readonly onMaterialLoadedObservable = new Observable<Material>();
+        public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
         public readonly onCompleteObservable = new Observable<IGLTFLoader>();
 
         public get state(): Nullable<GLTFLoaderState> {
             return this._state;
         }
 
-        public get extensions(): IGLTFLoaderExtensions {
-            return this.extensions;
-        }
-
-        constructor() {
-            for (const name of GLTFLoader._Names) {
-                const extension = GLTFLoader._Factories[name](this);
-                this._extensions[name] = extension;
-            }
-        }
-
         public dispose(): void {
             if (this._disposed) {
                 return;
@@ -125,6 +115,12 @@ module BABYLON.GLTF2 {
 
         private _loadAsync(nodes: Nullable<Array<ILoaderNode>>, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
             return Promise.resolve().then(() => {
+                for (const name of GLTFLoader._Names) {
+                    const extension = GLTFLoader._Factories[name](this);
+                    this._extensions[name] = extension;
+                    this.onExtensionLoadedObservable.notifyObservers(extension);
+                }
+
                 this._babylonScene = scene;
                 this._rootUrl = rootUrl;
                 this._progressCallback = onProgress;
@@ -155,20 +151,22 @@ module BABYLON.GLTF2 {
                     this._startAnimations();
 
                     Tools.SetImmediate(() => {
-                        Promise.all(this._completePromises).then(() => {
-                            this._releaseResources();
-                            this._state = GLTFLoaderState.Complete;
-                            this.onCompleteObservable.notifyObservers(this);
-                        }).catch(error => {
-                            Tools.Error("glTF Loader: " + error.message);
-                            this.dispose();
-                        });
+                        if (!this._disposed) {
+                            Promise.all(this._completePromises).then(() => {
+                                this._releaseResources();
+                                this._state = GLTFLoaderState.Complete;
+                                this.onCompleteObservable.notifyObservers(this);
+                            }).catch(error => {
+                                Tools.Error("glTF Loader: " + error.message);
+                                this.dispose();
+                            });
+                        }
                     });
-                }).catch(error => {
-                    Tools.Error("glTF Loader: " + error.message);
-                    this.dispose();
-                    throw error;
                 });
+            }).catch(error => {
+                Tools.Error("glTF Loader: " + error.message);
+                this.dispose();
+                throw error;
             });
         }
 
@@ -1279,9 +1277,13 @@ module BABYLON.GLTF2 {
 
             const deferred = new Deferred<void>();
             const babylonTexture = new Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
-                deferred.resolve();
+                if (!this._disposed) {
+                    deferred.resolve();
+                }
             }, (message, exception) => {
-                deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                if (!this._disposed) {
+                    deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                }
             });
             promises.push(deferred.promise);
 
@@ -1336,7 +1338,7 @@ module BABYLON.GLTF2 {
         }
 
         public _loadUriAsync(context: string, uri: string): Promise<ArrayBufferView> {
-            const promise = GLTFLoaderExtension._LoadUriAsync(this, context, uri); 
+            const promise = GLTFLoaderExtension._LoadUriAsync(this, context, uri);
             if (promise) {
                 return promise;
             }
@@ -1351,21 +1353,27 @@ module BABYLON.GLTF2 {
 
             return new Promise((resolve, reject) => {
                 const request = Tools.LoadFile(this._rootUrl + uri, data => {
-                    resolve(new Uint8Array(data as ArrayBuffer));
+                    if (!this._disposed) {
+                        resolve(new Uint8Array(data as ArrayBuffer));
+                    }
                 }, event => {
-                    try {
-                        if (request && this._state === GLTFLoaderState.Loading) {
-                            request._lengthComputable = event.lengthComputable;
-                            request._loaded = event.loaded;
-                            request._total = event.total;
-                            this._onProgress();
+                    if (!this._disposed) {
+                        try {
+                            if (request && this._state === GLTFLoaderState.Loading) {
+                                request._lengthComputable = event.lengthComputable;
+                                request._loaded = event.loaded;
+                                request._total = event.total;
+                                this._onProgress();
+                            }
+                        }
+                        catch (e) {
+                            reject(e);
                         }
-                    }
-                    catch (e) {
-                        reject(e);
                     }
                 }, this._babylonScene.database, true, (request, exception) => {
-                    reject(new LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                    if (!this._disposed) {
+                        reject(new LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                    }
                 }) as IFileRequestInfo;
 
                 this._requests.push(request);

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

@@ -1,7 +1,7 @@
 /// <reference path="../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2 {
-    export abstract class GLTFLoaderExtension {
+    export abstract class GLTFLoaderExtension implements IGLTFLoaderExtension {
         public enabled = true;
 
         protected _loader: GLTFLoader;
@@ -10,7 +10,7 @@ module BABYLON.GLTF2 {
             this._loader = loader;
         }
 
-        protected abstract get _name(): string;
+        public abstract readonly name: string;
 
         // #region Overridable Methods
 
@@ -36,17 +36,17 @@ module BABYLON.GLTF2 {
 
             const extensions = property.extensions;
 
-            const extension = extensions[this._name] as T;
+            const extension = extensions[this.name] as T;
             if (!extension) {
                 return null;
             }
 
             // Clear out the extension before executing the action to avoid recursing into the same property.
-            delete extensions[this._name];
+            delete extensions[this.name];
 
-            return actionAsync(context + "extensions/" + this._name, extension).then(() => {
+            return actionAsync(context + "/extensions/" + this.name, extension).then(() => {
                 // Restore the extension after completing the action.
-                extensions[this._name] = extension;
+                extensions[this.name] = extension;
             });
         }
 

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

@@ -35,18 +35,22 @@ module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
 
-    export enum GLTFLoaderState {
-        Loading,
-        Ready,
-        Complete
-    }
-
     export interface IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+
+        /**
+         * Whether this extension is enabled.
+         */
         enabled: boolean;
     }
 
-    export interface IGLTFLoaderExtensions {
-        [name: string]: IGLTFLoaderExtension;
+    export enum GLTFLoaderState {
+        Loading,
+        Ready,
+        Complete
     }
 
     export interface IGLTFLoader extends IDisposable {
@@ -56,14 +60,14 @@ module BABYLON {
         useClipPlane: boolean;
         compileShadowGenerators: boolean;
 
-        onDisposeObservable: Observable<IGLTFLoader>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
 
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
 
         importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }>;
         loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<void>;
@@ -195,17 +199,24 @@ module BABYLON {
         }
 
         /**
-         * The loader state or null if not active.
+         * Raised after a loader extension is created.
+         * Set additional options for a loader extension in this event.
          */
-        public get loaderState(): Nullable<GLTFLoaderState> {
-            return this._loader ? this._loader.state : null;
+        public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
+
+        private _onExtensionLoadedObserver: Nullable<Observer<IGLTFLoaderExtension>>;
+        public set onExtensionLoaded(callback: (extension: IGLTFLoaderExtension) => void) {
+            if (this._onExtensionLoadedObserver) {
+                this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
+            }
+            this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
         }
 
         /**
-         * The loader extensions or null if not active.
+         * The loader state or null if not active.
          */
-        public get loaderExtensions(): Nullable<IGLTFLoaderExtensions> {
-            return this._loader ? this._loader.extensions : null;
+        public get loaderState(): Nullable<GLTFLoaderState> {
+            return this._loader ? this._loader.state : null;
         }
 
         // #endregion
@@ -336,6 +347,7 @@ module BABYLON {
             loader.onTextureLoadedObservable.add(texture => this.onTextureLoadedObservable.notifyObservers(texture));
             loader.onMaterialLoadedObservable.add(material => this.onMaterialLoadedObservable.notifyObservers(material));
             loader.onCompleteObservable.add(() => this.onCompleteObservable.notifyObservers(this));
+            loader.onExtensionLoadedObservable.add(extension => this.onExtensionLoadedObservable.notifyObservers(extension));
             return loader;
         }
 

+ 3 - 7
src/Engine/babylon.engine.ts

@@ -2626,14 +2626,10 @@
             var name = vertex + "+" + fragment + "@" + (defines ? defines : (<EffectCreationOptions>attributesNamesOrOptions).defines);
             if (this._compiledEffects[name]) {
                 var compiledEffect = <Effect>this._compiledEffects[name];
-                if (onCompiled) {
-                    if (compiledEffect.isReady()) {
-                        onCompiled(compiledEffect);
-                    }
-                    else {
-                        compiledEffect.onCompileObservable.add(onCompiled, undefined, undefined, true);
-                    }
+                if (onCompiled && compiledEffect.isReady()) {
+                    onCompiled(compiledEffect);
                 }
+
                 return compiledEffect;
             }
             var effect = new Effect(baseName, attributesNamesOrOptions, uniformsNamesOrEngine, samplers, this, defines, fallbacks, onCompiled, onError, indexParameters);

+ 10 - 2
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -1226,11 +1226,19 @@
             };
 
             const defines = new PBRMaterialDefines();
-            this._prepareEffect(mesh, defines, () => {
+            const effect = this._prepareEffect(mesh, defines, undefined, undefined, undefined, localOptions.clipPlane)!;
+            if (effect.isReady()) {
                 if (onCompiled) {
                     onCompiled(this);
                 }
-            }, undefined, undefined, localOptions.clipPlane);
+            }
+            else {
+                effect.onCompileObservable.add(() => {
+                    if (onCompiled) {
+                        onCompiled(this);
+                    }
+                });
+            }
         }
 
         /**

+ 61 - 0
tests/unit/babylon/src/Material/babylon.material.tests.ts

@@ -0,0 +1,61 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon Material', function () {
+    let subject: BABYLON.Engine;
+
+    /**
+     * Loads the dependencies.
+     */
+    before(function (done) {
+        this.timeout(180000);
+        (BABYLONDEVTOOLS).Loader
+            .useDist()
+            .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
+                done();
+            });
+    });
+
+    /**
+     * Create a new engine subject before each test.
+     */
+    beforeEach(function () {
+        subject = new BABYLON.NullEngine({
+            renderHeight: 256,
+            renderWidth: 256,
+            textureSize: 256,
+            deterministicLockstep: false,
+            lockstepMaxSteps: 1
+        });
+    });
+
+    describe('#PBRMaterial', () => {
+        it('forceCompilation of a single material', () => {
+            const scene = new BABYLON.Scene(subject);
+            const mesh = BABYLON.Mesh.CreateBox("mesh", 1, scene);
+            const material = new BABYLON.PBRMaterial("material", scene);
+            return material.forceCompilationAsync(mesh);
+        });
+        it('forceCompilation of already compiled material', () => {
+            const scene = new BABYLON.Scene(subject);
+            const mesh = BABYLON.Mesh.CreateBox("mesh", 1, scene);
+            const material = new BABYLON.PBRMaterial("material", scene);
+            material.albedoTexture = new BABYLON.Texture("/Playground/scenes/BoomBox/BoomBox_baseColor.png", scene);
+            return material.forceCompilationAsync(mesh).then(() => {
+                return material.forceCompilationAsync(mesh);
+            });
+        });
+        it('forceCompilation of same material in parallel', () => {
+            const scene = new BABYLON.Scene(subject);
+            const mesh = BABYLON.Mesh.CreateBox("mesh", 1, scene);
+            const material = new BABYLON.PBRMaterial("material", scene);
+            material.albedoTexture = new BABYLON.Texture("/Playground/scenes/BoomBox/BoomBox_baseColor.png", scene);
+            return Promise.all([
+                material.forceCompilationAsync(mesh),
+                material.forceCompilationAsync(mesh)
+            ]);
+        });
+    });
+});

+ 1 - 0
tests/unit/karma.conf.js

@@ -16,6 +16,7 @@ module.exports = function (config) {
             './tests/unit/babylon/babylon.example.tests.js',
             './tests/unit/babylon/serializers/babylon.glTFSerializer.tests.js',
             './tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.js',
+            './tests/unit/babylon/src/Material/babylon.material.tests.js',
             './tests/unit/babylon/src/Mesh/babylon.mesh.vertexData.tests.js',
             './tests/unit/babylon/src/Tools/babylon.promise.tests.js',
             { pattern: 'dist/**/*', watched: false, included: false, served: true },