Selaa lähdekoodia

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 7 vuotta sitten
vanhempi
commit
58df97fe42

+ 14 - 0
Viewer/src/model/viewerModel.ts

@@ -102,6 +102,8 @@ export class ViewerModel implements IDisposable {
     private _animatables: Array<Animatable> = [];
     private _frameRate: number = 60;
 
+    private _shadowsRenderedAfterLoad: boolean = false;
+
     constructor(protected _viewer: AbstractViewer, modelConfiguration: IModelConfiguration) {
         this.onLoadedObservable = new Observable();
         this.onLoadErrorObservable = new Observable();
@@ -141,6 +143,18 @@ export class ViewerModel implements IDisposable {
         });
     }
 
+    public get shadowsRenderedAfterLoad() {
+        return this._shadowsRenderedAfterLoad;
+    }
+
+    public set shadowsRenderedAfterLoad(rendered: boolean) {
+        if (!rendered) {
+            throw new Error("can only be enabled");
+        } else {
+            this._shadowsRenderedAfterLoad = rendered;
+        }
+    }
+
     /**
      * Is this model enabled?
      */

+ 233 - 225
Viewer/src/viewer/sceneManager.ts

@@ -140,7 +140,13 @@ export class SceneManager {
                 if (scene.animatables && scene.animatables.length > 0) {
                     // make sure all models are loaded
                     updateShadows();
-                } else if (!(this.models.every((model) => model.state === ModelState.COMPLETE && !model.currentAnimation))) {
+                } else if (!(this.models.every((model) => {
+                    if (!model.shadowsRenderedAfterLoad) {
+                        model.shadowsRenderedAfterLoad = true;
+                        return false;
+                    }
+                    return model.state === ModelState.COMPLETE && !model.currentAnimation
+                }))) {
                     updateShadows();
                 }
             });
@@ -240,7 +246,7 @@ export class SceneManager {
         }
     }
 
-    private _groundMirrorEnabled = false;
+    private _groundMirrorEnabled = true;
     /**
      * gets wether the reflection is disabled.
      */
@@ -257,7 +263,7 @@ export class SceneManager {
 
         this._groundMirrorEnabled = value;
         if (this.environmentHelper && this.environmentHelper.groundMaterial && this.environmentHelper.groundMirror) {
-            if (value) {
+            if (!value) {
                 this.environmentHelper.groundMaterial.reflectionTexture = null;
             } else {
                 this.environmentHelper.groundMaterial.reflectionTexture = this.environmentHelper.groundMirror;
@@ -440,7 +446,7 @@ export class SceneManager {
     private _defaultRenderingPipelineShouldBuild: boolean = true;
 
     private _rebuildPostprocesses(configuration?: IDefaultRenderingPipelineConfiguration): void {
-        if (!this.defaultRenderingPipelineEnabled || !getConfigurationKey("scene.imageProcessingConfiguration.isEnabled", this._viewer.configuration)) {
+        if (!this._defaultRenderingPipelineEnabled || !getConfigurationKey("scene.imageProcessingConfiguration.isEnabled", this._viewer.configuration)) {
             if (this._defaultRenderingPipeline) {
                 this._defaultRenderingPipeline.dispose();
                 this._defaultRenderingPipeline = null;
@@ -812,159 +818,161 @@ export class SceneManager {
                 this.environmentHelper.dispose();
                 delete this.environmentHelper;
             };
-            return Promise.resolve(this.scene);
-        }
-
+        } else {
 
-        const options: Partial<IEnvironmentHelperOptions> = {
-            createGround: !!groundConfiguration && this._groundEnabled,
-            createSkybox: !!skyboxConifguration,
-            setupImageProcessing: false, // will be done at the scene level!,
-        };
 
-        // will that cause problems with model ground configuration?
-        /*if (model) {
-            const boundingInfo = model.rootMesh.getHierarchyBoundingVectors(true);
-            const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
-            const halfSizeVec = sizeVec.scale(0.5);
-            const center = boundingInfo.min.add(halfSizeVec);
-            options.groundYBias = -center.y;
-        }*/
+            const options: Partial<IEnvironmentHelperOptions> = {
+                createGround: !!groundConfiguration && this._groundEnabled,
+                createSkybox: !!skyboxConifguration,
+                setupImageProcessing: false, // will be done at the scene level!,
+            };
 
-        if (groundConfiguration) {
-            let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
+            // will that cause problems with model ground configuration?
+            /*if (model) {
+                const boundingInfo = model.rootMesh.getHierarchyBoundingVectors(true);
+                const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
+                const halfSizeVec = sizeVec.scale(0.5);
+                const center = boundingInfo.min.add(halfSizeVec);
+                options.groundYBias = -center.y;
+            }*/
+
+            if (groundConfiguration) {
+                let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
+
+                let groundSize = groundConfig.size || (typeof skyboxConifguration === 'object' && skyboxConifguration.scale);
+                if (groundSize) {
+                    options.groundSize = groundSize;
+                }
 
-            let groundSize = groundConfig.size || (typeof skyboxConifguration === 'object' && skyboxConifguration.scale);
-            if (groundSize) {
-                options.groundSize = groundSize;
-            }
+                options.enableGroundShadow = groundConfig === true || groundConfig.receiveShadows;
+                if (groundConfig.shadowLevel !== undefined) {
+                    options.groundShadowLevel = groundConfig.shadowLevel;
+                }
+                options.enableGroundMirror = !!groundConfig.mirror && this.groundMirrorEnabled;
+                if (groundConfig.texture) {
+                    options.groundTexture = this.labs.getAssetUrl(groundConfig.texture);
+                }
+                if (groundConfig.color) {
+                    options.groundColor = new Color3(groundConfig.color.r, groundConfig.color.g, groundConfig.color.b)
+                }
 
-            options.enableGroundShadow = groundConfig === true || groundConfig.receiveShadows;
-            if (groundConfig.shadowLevel !== undefined) {
-                options.groundShadowLevel = groundConfig.shadowLevel;
-            }
-            options.enableGroundMirror = !!groundConfig.mirror && this.groundMirrorEnabled;
-            if (groundConfig.texture) {
-                options.groundTexture = this.labs.getAssetUrl(groundConfig.texture);
-            }
-            if (groundConfig.color) {
-                options.groundColor = new Color3(groundConfig.color.r, groundConfig.color.g, groundConfig.color.b)
-            }
+                if (groundConfig.opacity !== undefined) {
+                    options.groundOpacity = groundConfig.opacity;
+                }
 
-            if (groundConfig.opacity !== undefined) {
-                options.groundOpacity = groundConfig.opacity;
+                if (groundConfig.mirror) {
+                    options.enableGroundMirror = true;
+                    // to prevent undefines
+                    if (typeof groundConfig.mirror === "object") {
+                        if (groundConfig.mirror.amount !== undefined)
+                            options.groundMirrorAmount = groundConfig.mirror.amount;
+                        if (groundConfig.mirror.sizeRatio !== undefined)
+                            options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
+                        if (groundConfig.mirror.blurKernel !== undefined)
+                            options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
+                        if (groundConfig.mirror.fresnelWeight !== undefined)
+                            options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
+                        if (groundConfig.mirror.fallOffDistance !== undefined)
+                            options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
+                        if (this._defaultPipelineTextureType !== undefined)
+                            options.groundMirrorTextureType = this._defaultPipelineTextureType;
+                    }
+                }
             }
 
-            if (groundConfig.mirror) {
-                options.enableGroundMirror = true;
-                // to prevent undefines
-                if (typeof groundConfig.mirror === "object") {
-                    if (groundConfig.mirror.amount !== undefined)
-                        options.groundMirrorAmount = groundConfig.mirror.amount;
-                    if (groundConfig.mirror.sizeRatio !== undefined)
-                        options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
-                    if (groundConfig.mirror.blurKernel !== undefined)
-                        options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
-                    if (groundConfig.mirror.fresnelWeight !== undefined)
-                        options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
-                    if (groundConfig.mirror.fallOffDistance !== undefined)
-                        options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
-                    if (this._defaultPipelineTextureType !== undefined)
-                        options.groundMirrorTextureType = this._defaultPipelineTextureType;
+            let postInitSkyboxMaterial = false;
+            if (skyboxConifguration) {
+                let conf = skyboxConifguration === true ? {} : skyboxConifguration;
+                if (conf.material && conf.material.imageProcessingConfiguration) {
+                    options.setupImageProcessing = false; // will be configured later manually.
+                }
+                let skyboxSize = conf.scale;
+                if (skyboxSize) {
+                    options.skyboxSize = skyboxSize;
+                }
+                options.sizeAuto = !options.skyboxSize;
+                if (conf.color) {
+                    options.skyboxColor = new Color3(conf.color.r, conf.color.g, conf.color.b)
+                }
+                if (conf.cubeTexture && conf.cubeTexture.url) {
+                    if (typeof conf.cubeTexture.url === "string") {
+                        options.skyboxTexture = this.labs.getAssetUrl(conf.cubeTexture.url);
+                    } else {
+                        // init later!
+                        postInitSkyboxMaterial = true;
+                    }
                 }
-            }
-        }
 
-        let postInitSkyboxMaterial = false;
-        if (skyboxConifguration) {
-            let conf = skyboxConifguration === true ? {} : skyboxConifguration;
-            if (conf.material && conf.material.imageProcessingConfiguration) {
-                options.setupImageProcessing = false; // will be configured later manually.
-            }
-            let skyboxSize = conf.scale;
-            if (skyboxSize) {
-                options.skyboxSize = skyboxSize;
-            }
-            options.sizeAuto = !options.skyboxSize;
-            if (conf.color) {
-                options.skyboxColor = new Color3(conf.color.r, conf.color.g, conf.color.b)
-            }
-            if (conf.cubeTexture && conf.cubeTexture.url) {
-                if (typeof conf.cubeTexture.url === "string") {
-                    options.skyboxTexture = this.labs.getAssetUrl(conf.cubeTexture.url);
-                } else {
-                    // init later!
+                if (conf.material) {
                     postInitSkyboxMaterial = true;
                 }
             }
 
-            if (conf.material) {
-                postInitSkyboxMaterial = true;
-            }
-        }
-
-        options.setupImageProcessing = false; // TMP
-
-        if (!this.environmentHelper) {
-            this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
-        } else {
-            // unlikely, but there might be a new scene! we need to dispose.
+            options.setupImageProcessing = false; // TMP
 
-            // get the scene used by the envHelper
-            let scene: Scene = this.environmentHelper.rootMesh.getScene();
-            // is it a different scene? Oh no!
-            if (scene !== this.scene) {
-                this.environmentHelper.dispose();
+            if (!this.environmentHelper) {
                 this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
             } else {
-                this.environmentHelper.updateOptions(options)!;
+                // unlikely, but there might be a new scene! we need to dispose.
+
+                // get the scene used by the envHelper
+                let scene: Scene = this.environmentHelper.rootMesh.getScene();
+                // is it a different scene? Oh no!
+                if (scene !== this.scene) {
+                    this.environmentHelper.dispose();
+                    this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
+                } else {
+                    this.environmentHelper.updateOptions(options)!;
+                }
             }
-        }
 
-        if (this.environmentHelper.rootMesh && this._viewer.configuration.scene && this._viewer.configuration.scene.environmentRotationY !== undefined) {
-            this.environmentHelper.rootMesh.rotation.y = this._viewer.configuration.scene.environmentRotationY;
-        }
-
-        let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
-        if (this.environmentHelper.groundMaterial && groundConfig) {
-            this.environmentHelper.groundMaterial._perceptualColor = this.mainColor;
-            if (groundConfig.material) {
-                extendClassWithConfig(this.environmentHelper.groundMaterial, groundConfig.material);
+            if (this.environmentHelper.rootMesh && this._viewer.configuration.scene && this._viewer.configuration.scene.environmentRotationY !== undefined) {
+                this.environmentHelper.rootMesh.rotation.y = this._viewer.configuration.scene.environmentRotationY;
             }
 
-            if (this.environmentHelper.groundMirror) {
-                const mirrorClearColor = this.environmentHelper.groundMaterial._perceptualColor.toLinearSpace();
-                // TODO user camera exposure value to set the mirror clear color
-                let exposure = Math.pow(2.0, -this.scene.imageProcessingConfiguration.exposure) * Math.PI;
-                mirrorClearColor.scaleToRef(1 / exposure, mirrorClearColor);
+            let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
+            if (this.environmentHelper.groundMaterial && groundConfig) {
+                this.environmentHelper.groundMaterial._perceptualColor = this.mainColor;
+                if (groundConfig.material) {
+                    extendClassWithConfig(this.environmentHelper.groundMaterial, groundConfig.material);
+                }
 
-                this.environmentHelper.groundMirror.clearColor.r = Scalar.Clamp(mirrorClearColor.r);
-                this.environmentHelper.groundMirror.clearColor.g = Scalar.Clamp(mirrorClearColor.g);
-                this.environmentHelper.groundMirror.clearColor.b = Scalar.Clamp(mirrorClearColor.b);
-                this.environmentHelper.groundMirror.clearColor.a = 1;
+                if (this.environmentHelper.groundMirror) {
+                    const mirrorClearColor = this.environmentHelper.groundMaterial._perceptualColor.toLinearSpace();
+                    // TODO user camera exposure value to set the mirror clear color
+                    let exposure = Math.pow(2.0, -this.scene.imageProcessingConfiguration.exposure) * Math.PI;
+                    mirrorClearColor.scaleToRef(1 / exposure, mirrorClearColor);
 
-                if (!this.groundMirrorEnabled) {
-                    this.environmentHelper.groundMaterial.reflectionTexture = null;
+                    this.environmentHelper.groundMirror.clearColor.r = Scalar.Clamp(mirrorClearColor.r);
+                    this.environmentHelper.groundMirror.clearColor.g = Scalar.Clamp(mirrorClearColor.g);
+                    this.environmentHelper.groundMirror.clearColor.b = Scalar.Clamp(mirrorClearColor.b);
+                    this.environmentHelper.groundMirror.clearColor.a = 1;
+
+                    if (!this.groundMirrorEnabled) {
+                        this.environmentHelper.groundMaterial.reflectionTexture = null;
+                    }
                 }
             }
-        }
 
 
-        let skyboxMaterial = this.environmentHelper.skyboxMaterial;
-        if (skyboxMaterial) {
-            skyboxMaterial._perceptualColor = this.mainColor;
+            let skyboxMaterial = this.environmentHelper.skyboxMaterial;
+            if (skyboxMaterial) {
+                skyboxMaterial._perceptualColor = this.mainColor;
 
-            if (postInitSkyboxMaterial) {
-                if (typeof skyboxConifguration === 'object' && skyboxConifguration.material) {
-                    extendClassWithConfig(skyboxMaterial, skyboxConifguration.material);
+                if (postInitSkyboxMaterial) {
+                    if (typeof skyboxConifguration === 'object' && skyboxConifguration.material) {
+                        extendClassWithConfig(skyboxMaterial, skyboxConifguration.material);
+                    }
                 }
             }
+
         }
 
         this._viewer.onModelLoadedObservable.add((model) => {
             this._updateGroundMirrorRenderList(model);
         });
 
+
         this.onEnvironmentConfiguredObservable.notifyObservers({
             sceneManager: this,
             object: this.environmentHelper,
@@ -987,136 +995,136 @@ export class SceneManager {
         if (!Object.keys(lightsConfiguration).length) {
             if (!this.scene.lights.length)
                 this.scene.createDefaultLight(true);
-            return;
-        };
-
-        let lightsAvailable: Array<string> = this.scene.lights.map(light => light.name);
-        // compare to the global (!) configuration object and dispose unneeded:
-        let lightsToConfigure = Object.keys(this._viewer.configuration.lights || []);
-        if (Object.keys(lightsToConfigure).length !== lightsAvailable.length) {
-            lightsAvailable.forEach(lName => {
-                if (lightsToConfigure.indexOf(lName) === -1) {
-                    this.scene.getLightByName(lName)!.dispose();
-                }
-            });
-        }
+        } else {
 
-        Object.keys(lightsConfiguration).forEach((name, idx) => {
-            let lightConfig: ILightConfiguration = { type: 0 };
-            if (typeof lightsConfiguration[name] === 'object') {
-                lightConfig = <ILightConfiguration>lightsConfiguration[name];
+            let lightsAvailable: Array<string> = this.scene.lights.map(light => light.name);
+            // compare to the global (!) configuration object and dispose unneeded:
+            let lightsToConfigure = Object.keys(this._viewer.configuration.lights || []);
+            if (Object.keys(lightsToConfigure).length !== lightsAvailable.length) {
+                lightsAvailable.forEach(lName => {
+                    if (lightsToConfigure.indexOf(lName) === -1) {
+                        this.scene.getLightByName(lName)!.dispose();
+                    }
+                });
             }
 
-            lightConfig.name = name;
+            Object.keys(lightsConfiguration).forEach((name, idx) => {
+                let lightConfig: ILightConfiguration = { type: 0 };
+                if (typeof lightsConfiguration[name] === 'object') {
+                    lightConfig = <ILightConfiguration>lightsConfiguration[name];
+                }
+
+                lightConfig.name = name;
 
-            let light: Light;
-            // light is not already available
-            if (lightsAvailable.indexOf(name) === -1) {
-                let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
-                if (!constructor) return;
-                light = constructor();
-            } else {
-                // available? get it from the scene
-                light = <Light>this.scene.getLightByName(name);
-                lightsAvailable = lightsAvailable.filter(ln => ln !== name);
-                if (lightConfig.type !== undefined && light.getTypeID() !== lightConfig.type) {
-                    light.dispose();
+                let light: Light;
+                // light is not already available
+                if (lightsAvailable.indexOf(name) === -1) {
                     let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
                     if (!constructor) return;
                     light = constructor();
+                } else {
+                    // available? get it from the scene
+                    light = <Light>this.scene.getLightByName(name);
+                    lightsAvailable = lightsAvailable.filter(ln => ln !== name);
+                    if (lightConfig.type !== undefined && light.getTypeID() !== lightConfig.type) {
+                        light.dispose();
+                        let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
+                        if (!constructor) return;
+                        light = constructor();
+                    }
                 }
-            }
 
-            // if config set the light to false, dispose it.
-            if (lightsConfiguration[name] === false) {
-                light.dispose();
-                return;
-            }
+                // if config set the light to false, dispose it.
+                if (lightsConfiguration[name] === false) {
+                    light.dispose();
+                    return;
+                }
 
-            //enabled
-            var enabled = lightConfig.enabled !== undefined ? lightConfig.enabled : !lightConfig.disabled;
-            light.setEnabled(enabled);
+                //enabled
+                var enabled = lightConfig.enabled !== undefined ? lightConfig.enabled : !lightConfig.disabled;
+                light.setEnabled(enabled);
 
 
-            extendClassWithConfig(light, lightConfig);
+                extendClassWithConfig(light, lightConfig);
 
 
 
-            //position. Some lights don't support shadows
-            if (light instanceof ShadowLight) {
-                // set default values
-                light.shadowMinZ = light.shadowMinZ || 0.2;
-                light.shadowMaxZ = Math.min(10, light.shadowMaxZ || 10); //large far clips reduce shadow depth precision
+                //position. Some lights don't support shadows
+                if (light instanceof ShadowLight) {
+                    // set default values
+                    light.shadowMinZ = light.shadowMinZ || 0.2;
+                    light.shadowMaxZ = Math.min(10, light.shadowMaxZ || 10); //large far clips reduce shadow depth precision
 
-                if (lightConfig.target) {
-                    if (light.setDirectionToTarget) {
-                        let target = Vector3.Zero().copyFrom(lightConfig.target as Vector3);
-                        light.setDirectionToTarget(target);
+                    if (lightConfig.target) {
+                        if (light.setDirectionToTarget) {
+                            let target = Vector3.Zero().copyFrom(lightConfig.target as Vector3);
+                            light.setDirectionToTarget(target);
+                        }
+                    } else if (lightConfig.direction) {
+                        let direction = Vector3.Zero().copyFrom(lightConfig.direction as Vector3);
+                        light.direction = direction;
                     }
-                } else if (lightConfig.direction) {
-                    let direction = Vector3.Zero().copyFrom(lightConfig.direction as Vector3);
-                    light.direction = direction;
-                }
 
-                let isShadowEnabled = false;
-                if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_DIRECTIONALLIGHT) {
-                    (<BABYLON.DirectionalLight>light).shadowFrustumSize = lightConfig.shadowFrustumSize || 2;
-                    isShadowEnabled = true;
-                }
-                else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_SPOTLIGHT) {
-                    let spotLight: BABYLON.SpotLight = <BABYLON.SpotLight>light;
-                    if (lightConfig.spotAngle !== undefined) {
-                        spotLight.angle = lightConfig.spotAngle * Math.PI / 180;
-                    }
-                    if (spotLight.angle && lightConfig.shadowFieldOfView) {
-                        spotLight.shadowAngleScale = lightConfig.shadowFieldOfView / spotLight.angle;
+                    let isShadowEnabled = false;
+                    if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_DIRECTIONALLIGHT) {
+                        (<BABYLON.DirectionalLight>light).shadowFrustumSize = lightConfig.shadowFrustumSize || 2;
+                        isShadowEnabled = true;
                     }
-                    isShadowEnabled = true;
-                }
-                else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_POINTLIGHT) {
-                    if (lightConfig.shadowFieldOfView) {
-                        (<BABYLON.PointLight>light).shadowAngle = lightConfig.shadowFieldOfView * Math.PI / 180;
+                    else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_SPOTLIGHT) {
+                        let spotLight: BABYLON.SpotLight = <BABYLON.SpotLight>light;
+                        if (lightConfig.spotAngle !== undefined) {
+                            spotLight.angle = lightConfig.spotAngle * Math.PI / 180;
+                        }
+                        if (spotLight.angle && lightConfig.shadowFieldOfView) {
+                            spotLight.shadowAngleScale = lightConfig.shadowFieldOfView / spotLight.angle;
+                        }
+                        isShadowEnabled = true;
                     }
-                    isShadowEnabled = true;
-                }
-
-                let shadowGenerator = <BABYLON.ShadowGenerator>light.getShadowGenerator();
-                if (isShadowEnabled && lightConfig.shadowEnabled && this._maxShadows) {
-                    let bufferSize = lightConfig.shadowBufferSize || 256;
-
-                    if (!shadowGenerator) {
-                        shadowGenerator = new ShadowGenerator(bufferSize, light);
+                    else if (light.getTypeID() === BABYLON.Light.LIGHTTYPEID_POINTLIGHT) {
+                        if (lightConfig.shadowFieldOfView) {
+                            (<BABYLON.PointLight>light).shadowAngle = lightConfig.shadowFieldOfView * Math.PI / 180;
+                        }
+                        isShadowEnabled = true;
                     }
 
-                    var blurKernel = this.getBlurKernel(light, bufferSize);
-                    shadowGenerator.bias = this._shadowGeneratorBias;
-                    shadowGenerator.blurKernel = blurKernel;
-                    //override defaults
-                    extendClassWithConfig(shadowGenerator, lightConfig.shadowConfig || {});
+                    let shadowGenerator = <BABYLON.ShadowGenerator>light.getShadowGenerator();
+                    if (isShadowEnabled && lightConfig.shadowEnabled && this._maxShadows) {
+                        let bufferSize = lightConfig.shadowBufferSize || 256;
 
-                    // add the focues meshes to the shadow list
-                    this._viewer.onModelLoadedObservable.add((model) => {
-                        this._updateShadowRenderList(shadowGenerator, model);
-                    });
+                        if (!shadowGenerator) {
+                            shadowGenerator = new ShadowGenerator(bufferSize, light);
+                        }
 
-                    //if (model) {
-                    this._updateShadowRenderList(shadowGenerator);
-                    //}
-                } else if (shadowGenerator) {
-                    shadowGenerator.dispose();
+                        var blurKernel = this.getBlurKernel(light, bufferSize);
+                        shadowGenerator.bias = this._shadowGeneratorBias;
+                        shadowGenerator.blurKernel = blurKernel;
+                        //override defaults
+                        extendClassWithConfig(shadowGenerator, lightConfig.shadowConfig || {});
+
+                        // add the focues meshes to the shadow list
+                        this._viewer.onModelLoadedObservable.add((model) => {
+                            this._updateShadowRenderList(shadowGenerator, model);
+                        });
+
+                        //if (model) {
+                        this._updateShadowRenderList(shadowGenerator);
+                        //}
+                    } else if (shadowGenerator) {
+                        shadowGenerator.dispose();
+                    }
                 }
-            }
-        });
+            });
 
-        // render priority
-        let globalLightsConfiguration = this._viewer.configuration.lights || {};
-        Object.keys(globalLightsConfiguration).sort().forEach((name, idx) => {
-            let configuration = globalLightsConfiguration[name];
-            let light = this.scene.getLightByName(name);
-            // sanity check
-            if (!light) return;
-            light.renderPriority = -idx;
-        });
+            // render priority
+            let globalLightsConfiguration = this._viewer.configuration.lights || {};
+            Object.keys(globalLightsConfiguration).sort().forEach((name, idx) => {
+                let configuration = globalLightsConfiguration[name];
+                let light = this.scene.getLightByName(name);
+                // sanity check
+                if (!light) return;
+                light.renderPriority = -idx;
+            });
+        }
 
         this.onLightsConfiguredObservable.notifyObservers({
             sceneManager: this,

+ 1 - 0
Viewer/tests/unit/src/index.ts

@@ -8,4 +8,5 @@ import './configuration/mappers';
 import './configuration/loader';
 import './helper';
 import './configuration/updateConfiguration';
+import './viewer/sceneManager';
 export * from '../../../src'

+ 194 - 0
Viewer/tests/unit/src/viewer/sceneManager.ts

@@ -0,0 +1,194 @@
+import { Helper } from "../../../commons/helper";
+import { assert, expect, should } from "../viewerReference";
+import { DefaultViewer, AbstractViewer, Version, viewerManager } from "../../../../src";
+
+export let name = "scene manager";
+
+describe(name, function () {
+
+    it("should be initialized when a viewer is created", (done) => {
+        let viewer = Helper.getNewViewerInstance();
+        assert.isDefined(viewer.sceneManager, "scene manager should be defined");
+        viewer.dispose();
+        done();
+    })
+
+    it("should have objects initialized after init", (done) => {
+        let viewer = Helper.getNewViewerInstance();
+        viewer.onInitDoneObservable.add(() => {
+            assert.isDefined(viewer.sceneManager.scene);
+            assert.isDefined(viewer.sceneManager.labs);
+            assert.isDefined(viewer.sceneManager.scene.animationPropertiesOverride);
+            assert.isDefined(viewer.sceneManager.camera);
+            assert.isDefined(viewer.sceneManager.mainColor);
+            assert.isDefined(viewer.sceneManager.reflectionColor);
+            // default is white
+            ["r", "g", "b"].forEach(l => {
+                assert.equal(viewer.sceneManager.mainColor[l], 1);
+                assert.equal(viewer.sceneManager.reflectionColor[l], 1);
+            });
+            assert.isArray(viewer.sceneManager.models);
+            assert.isEmpty(viewer.sceneManager.models);
+
+            viewer.dispose();
+            done();
+        });
+    });
+
+    it("should set the default material to be PBR-Enabled per default", (done) => {
+        let viewer = Helper.getNewViewerInstance();
+        viewer.onInitDoneObservable.add(() => {
+            assert.isTrue(viewer.sceneManager.scene.defaultMaterial instanceof BABYLON.PBRMaterial);
+
+            viewer.dispose();
+            done();
+        });
+    });
+
+    it("should call observers correctly", (done) => {
+        let viewer = Helper.getNewViewerInstance(undefined, { extends: "none" });
+        let sceneInitCalled = false;
+        viewer.runRenderLoop = false;
+        viewer.sceneManager.onSceneInitObservable.clear();
+        viewer.sceneManager.onSceneInitObservable.add(() => {
+            assert.isDefined(viewer.sceneManager.scene);
+            assert.isUndefined(viewer.sceneManager.camera);
+            sceneInitCalled = true;
+        });
+
+        const s: string[] = [];
+
+        let update = (str: string, data) => {
+            if (s.indexOf(str) !== -1) {
+                assert.fail(false, true, str + " observer already called");
+                return false;
+            } else {
+                s.push(str);
+                return true;
+            }
+        }
+
+        viewer.sceneManager.onCameraConfiguredObservable.add(update.bind(null, "camera"));
+        viewer.sceneManager.onLightsConfiguredObservable.add(update.bind(null, "light"));
+        viewer.sceneManager.onEnvironmentConfiguredObservable.add(update.bind(null, "env"));
+        viewer.sceneManager.onSceneConfiguredObservable.add(update.bind(null, "scene"));
+        viewer.sceneManager.onSceneOptimizerConfiguredObservable.add(update.bind(null, "optimizer"));
+
+        viewer.onInitDoneObservable.add(() => {
+            viewer.updateConfiguration({
+                scene: {},
+                optimizer: false,
+                skybox: false
+            });
+            assert.isTrue(sceneInitCalled);
+            assert.lengthOf(s, 5);
+            viewer.dispose();
+            done();
+        });
+    });
+
+    it("should delete and rebuild post process pipeline when enabled and disabled", (done) => {
+        let viewer = Helper.getNewViewerInstance(undefined, {
+            scene: {
+                imageProcessingConfiguration: {
+                    isEnabled: true
+                }
+            },
+            lab: {
+                defaultRenderingPipelines: true
+            }
+        });
+
+        viewer.runRenderLoop = false;
+        viewer.sceneManager.onSceneInitObservable.clear();
+        viewer.sceneManager.onSceneInitObservable.add((scene) => {
+            viewer.onSceneInitObservable.notifyObserversWithPromise(scene);
+        })
+
+        viewer.onInitDoneObservable.add(() => {
+            assert.isDefined(viewer.sceneManager.defaultRenderingPipeline);
+            assert.isTrue(viewer.sceneManager.defaultRenderingPipelineEnabled);
+
+            viewer.sceneManager.defaultRenderingPipelineEnabled = false;
+
+            assert.isNull(viewer.sceneManager.defaultRenderingPipeline);
+            assert.isFalse(viewer.sceneManager.defaultRenderingPipelineEnabled);
+            assert.isFalse(viewer.sceneManager.scene.imageProcessingConfiguration.applyByPostProcess);
+
+            viewer.sceneManager.defaultRenderingPipelineEnabled = true;
+
+            assert.isDefined(viewer.sceneManager.defaultRenderingPipeline);
+            assert.isTrue(viewer.sceneManager.defaultRenderingPipelineEnabled);
+            assert.isTrue(viewer.sceneManager.scene.imageProcessingConfiguration.applyByPostProcess);
+
+            viewer.dispose();
+            done();
+        });
+    });
+
+    it("should allow disabling and enabling ground", (done) => {
+        let viewer = Helper.getNewViewerInstance(undefined, {
+            ground: true
+        });
+
+        viewer.onInitDoneObservable.add(() => {
+            // ground should be defined, and mirror should be enabled
+            assert.isDefined(viewer.sceneManager.environmentHelper.ground);
+
+            viewer.sceneManager.groundEnabled = false;
+
+            assert.isFalse(viewer.sceneManager.environmentHelper.ground!.isEnabled());
+
+            viewer.sceneManager.groundEnabled = true;
+
+            assert.isTrue(viewer.sceneManager.environmentHelper.ground!.isEnabled());
+
+            viewer.updateConfiguration({
+                ground: false
+            });
+
+            assert.isUndefined(viewer.sceneManager.environmentHelper);
+            assert.isTrue(viewer.sceneManager.groundEnabled);
+
+            viewer.dispose();
+            done();
+        });
+    });
+
+    it("should allow disabling and enabling ground texture", (done) => {
+        let viewer = Helper.getNewViewerInstance(undefined, {
+            ground: {
+                mirror: true
+            }
+        });
+
+        viewer.onInitDoneObservable.add(() => {
+            // ground should be defined, and mirror should be enabled
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial);
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial!.reflectionTexture);
+
+            viewer.sceneManager.groundMirrorEnabled = false;
+
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial);
+            assert.isNull(viewer.sceneManager.environmentHelper.groundMaterial!.reflectionTexture);
+
+            viewer.sceneManager.groundMirrorEnabled = true;
+
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial);
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial!.reflectionTexture);
+
+            viewer.updateConfiguration({
+                ground: {
+                    mirror: false
+                }
+            });
+
+            assert.isDefined(viewer.sceneManager.environmentHelper.groundMaterial);
+            assert.isNull(viewer.sceneManager.environmentHelper.groundMaterial!.reflectionTexture);
+            assert.isTrue(viewer.sceneManager.groundMirrorEnabled);
+
+            viewer.dispose();
+            done();
+        });
+    });
+});

+ 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);