Browse Source

introducing the scene manager
The scene manager will be in charge of execution scene-related functions:
Light, camera, model configuration, envirnoment, pipelines and more.

Raanan Weber 7 years ago
parent
commit
9a3012f756

+ 24 - 0
Viewer/src/helper.ts

@@ -25,4 +25,28 @@ export function kebabToCamel(s) {
  */
 export function camelToKebab(str) {
     return !str ? null : str.replace(/([A-Z])/g, function (g) { return '-' + g[0].toLowerCase() });
+}
+
+/**
+ * This will extend an object with configuration values.
+ * What it practically does it take the keys from the configuration and set them on the object.
+ * I the configuration is a tree, it will traverse into the tree.
+ * @param object the object to extend
+ * @param config the configuration object that will extend the object
+ */
+export function extendClassWithConfig(object: any, config: any) {
+    if (!config) return;
+    Object.keys(config).forEach(key => {
+        if (key in object && typeof object[key] !== 'function') {
+            // if (typeof object[key] === 'function') return;
+            // if it is an object, iterate internally until reaching basic types
+            if (typeof object[key] === 'object') {
+                extendClassWithConfig(object[key], config[key]);
+            } else {
+                if (config[key] !== undefined) {
+                    object[key] = config[key];
+                }
+            }
+        }
+    });
 }

+ 1 - 1
Viewer/src/model/modelLoader.ts

@@ -44,7 +44,7 @@ export class ModelLoader {
         let base = modelConfiguration.root || Tools.GetFolderPath(modelConfiguration.url);
         let plugin = modelConfiguration.loader;
 
-        model.loader = SceneLoader.ImportMesh(undefined, base, filename, this._viewer.scene, (meshes, particleSystems, skeletons, animationGroups) => {
+        model.loader = SceneLoader.ImportMesh(undefined, base, filename, this._viewer.sceneManager.scene, (meshes, particleSystems, skeletons, animationGroups) => {
             meshes.forEach(mesh => {
                 Tags.AddTagsTo(mesh, "viewerMesh");
                 model.addMesh(mesh);

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

@@ -88,13 +88,13 @@ export class ViewerModel implements IDisposable {
 
         this.state = ModelState.INIT;
 
-        this.rootMesh = new AbstractMesh("modelRootMesh", this._viewer.scene);
+        this.rootMesh = new AbstractMesh("modelRootMesh", this._viewer.sceneManager.scene);
 
         this._animations = [];
         //create a copy of the configuration to make sure it doesn't change even after it is changed in the viewer
         this._modelConfiguration = deepmerge({}, modelConfiguration);
 
-        this._viewer.models.push(this);
+        this._viewer.sceneManager.models.push(this);
         this._viewer.onModelAddedObservable.notifyObservers(this);
         this.onLoadedObservable.add(() => {
             this._configureModel();
@@ -162,7 +162,7 @@ export class ViewerModel implements IDisposable {
         // check if this is not a gltf loader and init the animations
         if (this.loader.name !== 'gltf') {
             this.skeletons.forEach((skeleton, idx) => {
-                let ag = new AnimationGroup("animation-" + idx, this._viewer.scene);
+                let ag = new AnimationGroup("animation-" + idx, this._viewer.sceneManager.scene);
                 skeleton.getAnimatables().forEach(a => {
                     if (a.animations[0]) {
                         ag.addTargetedAnimation(a.animations[0], a);
@@ -346,7 +346,7 @@ export class ViewerModel implements IDisposable {
      * Will remove this model from the viewer (but NOT dispose it).
      */
     public remove() {
-        this._viewer.models.splice(this._viewer.models.indexOf(this), 1);
+        this._viewer.sceneManager.models.splice(this._viewer.sceneManager.models.indexOf(this), 1);
         // hide it
         this.rootMesh.isVisible = false;
         this._viewer.onModelRemovedObservable.notifyObservers(this);

+ 14 - 16
Viewer/src/viewer/defaultViewer.ts

@@ -6,6 +6,7 @@ import { AbstractViewer } from './viewer';
 import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight } from 'babylonjs';
 import { CameraBehavior } from '../interfaces';
 import { ViewerModel } from '../model/viewerModel';
+import { extendClassWithConfig } from '../helper';
 
 /**
  * The Default viewer is the default implementation of the AbstractViewer.
@@ -21,15 +22,13 @@ export class DefaultViewer extends AbstractViewer {
     constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) {
         super(containerElement, initialConfiguration);
         this.onModelLoadedObservable.add(this._onModelLoaded);
-    }
+        this.sceneManager.onSceneInitObservable.add(() => {
+            extendClassWithConfig(this.sceneManager.scene, this._configuration.scene);
+            return this.sceneManager.scene;
+        });
 
-    /**
-     * Overriding the AbstractViewer's _initScene fcuntion
-     */
-    protected _initScene(): Promise<Scene> {
-        return super._initScene().then(() => {
-            this._extendClassWithConfig(this.scene, this._configuration.scene);
-            return this.scene;
+        this.sceneManager.onLightsConfiguredObservable.add((data) => {
+            this._configureLights(data.newConfiguration, data.model!);
         })
     }
 
@@ -310,8 +309,7 @@ export class DefaultViewer extends AbstractViewer {
      * @param lightsConfiguration the light configuration to use
      * @param model the model that will be used to configure the lights (if the lights are model-dependant)
      */
-    protected _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model: ViewerModel) {
-        super._configureLights(lightsConfiguration, model);
+    private _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
         // labs feature - flashlight
         if (this._configuration.lab && this._configuration.lab.flashlight) {
             let pointerPosition = Vector3.Zero();
@@ -323,7 +321,7 @@ export class DefaultViewer extends AbstractViewer {
                 angle = this._configuration.lab.flashlight.angle || angle;
             }
             var flashlight = new SpotLight("flashlight", Vector3.Zero(),
-                Vector3.Zero(), exponent, angle, this.scene);
+                Vector3.Zero(), exponent, angle, this.sceneManager.scene);
             if (typeof this._configuration.lab.flashlight === "object") {
                 flashlight.intensity = this._configuration.lab.flashlight.intensity || flashlight.intensity;
                 if (this._configuration.lab.flashlight.diffuse) {
@@ -338,8 +336,8 @@ export class DefaultViewer extends AbstractViewer {
                 }
 
             }
-            this.scene.constantlyUpdateMeshUnderPointer = true;
-            this.scene.onPointerObservable.add((eventData, eventState) => {
+            this.sceneManager.scene.constantlyUpdateMeshUnderPointer = true;
+            this.sceneManager.scene.onPointerObservable.add((eventData, eventState) => {
                 if (eventData.type === 4 && eventData.pickInfo) {
                     lightTarget = (eventData.pickInfo.pickedPoint);
                 } else {
@@ -347,14 +345,14 @@ export class DefaultViewer extends AbstractViewer {
                 }
             });
             let updateFlashlightFunction = () => {
-                if (this.camera && flashlight) {
-                    flashlight.position.copyFrom(this.camera.position);
+                if (this.sceneManager.camera && flashlight) {
+                    flashlight.position.copyFrom(this.sceneManager.camera.position);
                     if (lightTarget) {
                         lightTarget.subtractToRef(flashlight.position, flashlight.direction);
                     }
                 }
             }
-            this.scene.registerBeforeRender(updateFlashlightFunction);
+            this.sceneManager.scene.registerBeforeRender(updateFlashlightFunction);
             this._registeredOnBeforeRenderFunctions.push(updateFlashlightFunction);
         }
     }

+ 695 - 0
Viewer/src/viewer/sceneManager.ts

@@ -0,0 +1,695 @@
+import { Scene, ArcRotateCamera, Engine, Light, ShadowLight, Vector3, ShadowGenerator, Tags, CubeTexture, Quaternion, SceneOptimizer, EnvironmentHelper, SceneOptimizerOptions, Color3, IEnvironmentHelperOptions, AbstractMesh, FramingBehavior, Behavior, Observable } from 'babylonjs';
+import { AbstractViewer } from './viewer';
+import { ILightConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, ICameraConfiguration, ISkyboxConfiguration, ViewerConfiguration, IGroundConfiguration, IModelConfiguration } from '../configuration/configuration';
+import { ViewerModel } from '../model/viewerModel';
+import { extendClassWithConfig } from '../helper';
+import { CameraBehavior } from '../interfaces';
+
+/**
+ * This interface describes the structure of the variable sent with the configuration observables of the scene manager.
+ * O - the type of object we are dealing with (Light, ArcRotateCamera, Scene, etc')
+ * T - the configuration type
+ */
+export interface IPostConfigurationCallback<OBJ, CONF> {
+    newConfiguration: CONF;
+    sceneManager: SceneManager;
+    object: OBJ;
+    model?: ViewerModel;
+}
+
+export class SceneManager {
+
+    //Observers
+    onSceneInitObservable: Observable<Scene>;
+    onSceneConfiguredObservable: Observable<IPostConfigurationCallback<Scene, ISceneConfiguration>>;
+    onSceneOptimizerConfiguredObservable: Observable<IPostConfigurationCallback<SceneOptimizer, ISceneOptimizerConfiguration | boolean>>;
+    onCameraConfiguredObservable: Observable<IPostConfigurationCallback<ArcRotateCamera, ICameraConfiguration>>;
+    onLightsConfiguredObservable: Observable<IPostConfigurationCallback<Array<Light>, { [name: string]: ILightConfiguration | boolean }>>;
+    onModelsConfiguredObservable: Observable<IPostConfigurationCallback<Array<ViewerModel>, IModelConfiguration>>;
+    onEnvironmentConfiguredObservable: Observable<IPostConfigurationCallback<EnvironmentHelper, { skybox?: ISkyboxConfiguration | boolean, ground?: IGroundConfiguration | boolean }>>;
+
+    /**
+     * The Babylon Scene of this viewer
+     */
+    public scene: Scene;
+    /**
+     * The camera used in this viewer
+     */
+    public camera: ArcRotateCamera;
+    /**
+     * Babylon's scene optimizer
+     */
+    public sceneOptimizer: SceneOptimizer;
+    /**
+     * Models displayed in this viewer.
+     */
+    public models: Array<ViewerModel>;
+    /**
+     * Babylon's environment helper of this viewer
+     */
+    public environmentHelper: EnvironmentHelper;
+
+
+    //The following are configuration objects, default values.
+    protected _defaultHighpTextureType: number;
+    protected _shadowGeneratorBias: number;
+    protected _defaultPipelineTextureType: number;
+
+    /**
+     * The maximum number of shadows supported by the curent viewer
+     */
+    protected _maxShadows: number;
+    /**
+     * is HDR supported?
+     */
+    private _hdrSupport: boolean;
+
+    private _globalConfiguration: ViewerConfiguration;
+
+
+    /**
+     * Returns a boolean representing HDR support
+     */
+    public get isHdrSupported() {
+        return this._hdrSupport;
+    }
+
+    constructor(private _viewer: AbstractViewer) {
+        this.models = [];
+
+        this.onCameraConfiguredObservable = new Observable();
+        this.onLightsConfiguredObservable = new Observable();
+        this.onModelsConfiguredObservable = new Observable();
+        this.onSceneConfiguredObservable = new Observable();
+        this.onSceneInitObservable = new Observable();
+        this.onSceneOptimizerConfiguredObservable = new Observable();
+        this.onEnvironmentConfiguredObservable = new Observable();
+
+        this._viewer.onEngineInitObservable.add(() => {
+            this._handleHardwareLimitations();
+        });
+
+        this._viewer.onModelLoadedObservable.add((model) => {
+            this._configureLights(this._viewer.configuration.lights, model);
+
+            if (this._viewer.configuration.camera || !this.scene.activeCamera) {
+                this._configureCamera(this._viewer.configuration.camera || {}, model);
+            }
+            return this._initEnvironment(model);
+        })
+    }
+
+    /**
+     * initialize the environment for a specific model.
+     * Per default it will use the viewer's configuration.
+     * @param model the model to use to configure the environment.
+     * @returns a Promise that will resolve when the configuration is done.
+     */
+    protected _initEnvironment(model?: ViewerModel): Promise<Scene> {
+        this._configureEnvironment(this._viewer.configuration.skybox, this._viewer.configuration.ground, model);
+
+        return Promise.resolve(this.scene);
+    }
+
+    /**
+     * initialize the scene. Calling this function again will dispose the old scene, if exists.
+     */
+    public initScene(sceneConfiguration?: ISceneConfiguration, optimizerConfiguration?: boolean | ISceneOptimizerConfiguration): Promise<Scene> {
+
+        // if the scen exists, dispose it.
+        if (this.scene) {
+            this.scene.dispose();
+        }
+
+        // create a new scene
+        this.scene = new Scene(this._viewer.engine);
+
+        if (sceneConfiguration) {
+            this._configureScene(sceneConfiguration);
+
+            // Scene optimizer
+            if (optimizerConfiguration) {
+                this._configureOptimizer(optimizerConfiguration);
+            }
+        }
+
+        return this.onSceneInitObservable.notifyObserversWithPromise(this.scene);
+    }
+
+    public clearScene(clearModels: boolean = true, clearLights: boolean = false) {
+        if (clearModels) {
+            this.models.forEach(m => m.dispose());
+            this.models.length = 0;
+        }
+        if (clearLights) {
+            this.scene.lights.forEach(l => l.dispose());
+        }
+    }
+
+    /**
+     * This will update the scene's configuration, including camera, lights, environment.
+     * @param newConfiguration the delta that should be configured. This includes only the changes
+     * @param globalConfiguration The global configuration object, after the new configuration was merged into it
+     */
+    public updateConfiguration(newConfiguration: Partial<ViewerConfiguration>, globalConfiguration: ViewerConfiguration) {
+
+        // update scene configuration
+        if (newConfiguration.scene) {
+            this._configureScene(newConfiguration.scene);
+        }
+        // optimizer
+        if (newConfiguration.optimizer) {
+            this._configureOptimizer(newConfiguration.optimizer);
+        }
+
+        // lights
+        this._configureLights(newConfiguration.lights);
+
+        // environment
+        if (newConfiguration.skybox !== undefined || newConfiguration.ground !== undefined) {
+            this._configureEnvironment(newConfiguration.skybox, newConfiguration.ground);
+        }
+
+        // configure model
+        if (newConfiguration.model && typeof newConfiguration.model === 'object') {
+            this._configureModel(newConfiguration.model);
+        }
+
+        // camera
+        this._configureCamera(newConfiguration.camera);
+    }
+
+
+
+    /**
+     * internally configure the scene using the provided configuration.
+     * The scene will not be recreated, but just updated.
+     * @param sceneConfig the (new) scene configuration
+     */
+    protected _configureScene(sceneConfig: ISceneConfiguration) {
+        // sanity check!
+        if (!this.scene) {
+            return;
+        }
+        if (sceneConfig.debug) {
+            this.scene.debugLayer.show();
+        } else {
+            if (this.scene.debugLayer.isVisible()) {
+                this.scene.debugLayer.hide();
+            }
+        }
+
+        if (sceneConfig.clearColor) {
+            let cc = sceneConfig.clearColor;
+            let oldcc = this.scene.clearColor;
+            if (cc.r !== undefined) {
+                oldcc.r = cc.r;
+            }
+            if (cc.g !== undefined) {
+                oldcc.g = cc.g
+            }
+            if (cc.b !== undefined) {
+                oldcc.b = cc.b
+            }
+            if (cc.a !== undefined) {
+                oldcc.a = cc.a
+            }
+        }
+
+        // image processing configuration - optional.
+        if (sceneConfig.imageProcessingConfiguration) {
+            extendClassWithConfig(this.scene.imageProcessingConfiguration, sceneConfig.imageProcessingConfiguration);
+        }
+        if (sceneConfig.environmentTexture) {
+            if (this.scene.environmentTexture) {
+                this.scene.environmentTexture.dispose();
+            }
+            const environmentTexture = CubeTexture.CreateFromPrefilteredData(sceneConfig.environmentTexture, this.scene);
+            this.scene.environmentTexture = environmentTexture;
+        }
+
+        if (sceneConfig.autoRotate) {
+            this.camera.useAutoRotationBehavior = true;
+        }
+
+        this.onSceneConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.scene,
+            newConfiguration: sceneConfig
+        });
+    }
+
+    /**
+     * Configure the scene optimizer.
+     * The existing scene optimizer will be disposed and a new one will be created.
+     * @param optimizerConfig the (new) optimizer configuration
+     */
+    protected _configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean) {
+        if (typeof optimizerConfig === 'boolean') {
+            if (this.sceneOptimizer) {
+                this.sceneOptimizer.stop();
+                this.sceneOptimizer.dispose();
+                delete this.sceneOptimizer;
+            }
+            if (optimizerConfig) {
+                this.sceneOptimizer = new SceneOptimizer(this.scene);
+                this.sceneOptimizer.start();
+            }
+        } else {
+            let optimizerOptions: SceneOptimizerOptions = new SceneOptimizerOptions(optimizerConfig.targetFrameRate, optimizerConfig.trackerDuration);
+            // check for degradation
+            if (optimizerConfig.degradation) {
+                switch (optimizerConfig.degradation) {
+                    case "low":
+                        optimizerOptions = SceneOptimizerOptions.LowDegradationAllowed(optimizerConfig.targetFrameRate);
+                        break;
+                    case "moderate":
+                        optimizerOptions = SceneOptimizerOptions.ModerateDegradationAllowed(optimizerConfig.targetFrameRate);
+                        break;
+                    case "hight":
+                        optimizerOptions = SceneOptimizerOptions.HighDegradationAllowed(optimizerConfig.targetFrameRate);
+                        break;
+                }
+            }
+            if (this.sceneOptimizer) {
+                this.sceneOptimizer.stop();
+                this.sceneOptimizer.dispose()
+            }
+            this.sceneOptimizer = new SceneOptimizer(this.scene, optimizerOptions, optimizerConfig.autoGeneratePriorities, optimizerConfig.improvementMode);
+            this.sceneOptimizer.start();
+        }
+
+        this.onSceneOptimizerConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.sceneOptimizer,
+            newConfiguration: optimizerConfig
+        });
+    }
+
+    /**
+     * configure all models using the configuration.
+     * @param modelConfiguration the configuration to use to reconfigure the models
+     */
+    protected _configureModel(modelConfiguration: Partial<IModelConfiguration>) {
+        this.models.forEach(model => {
+            model.updateConfiguration(modelConfiguration);
+        });
+
+        this.onModelsConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.models,
+            newConfiguration: modelConfiguration
+        });
+    }
+
+    /**
+     * (Re) configure the camera. The camera will only be created once and from this point will only be reconfigured.
+     * @param cameraConfig the new camera configuration
+     * @param model optionally use the model to configure the camera.
+     */
+    protected _configureCamera(cameraConfig: ICameraConfiguration = {}, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
+
+        if (!this.scene.activeCamera) {
+            this.scene.createDefaultCamera(true, true, true);
+            this.camera = <ArcRotateCamera>this.scene.activeCamera!;
+        }
+        if (cameraConfig.position) {
+            this.camera.position.copyFromFloats(cameraConfig.position.x || 0, cameraConfig.position.y || 0, cameraConfig.position.z || 0);
+        }
+
+        if (cameraConfig.rotation) {
+            this.camera.rotationQuaternion = new Quaternion(cameraConfig.rotation.x || 0, cameraConfig.rotation.y || 0, cameraConfig.rotation.z || 0, cameraConfig.rotation.w || 0)
+        }
+
+        extendClassWithConfig(this.camera, cameraConfig);
+
+        this.camera.minZ = cameraConfig.minZ || this.camera.minZ;
+        this.camera.maxZ = cameraConfig.maxZ || this.camera.maxZ;
+
+        if (cameraConfig.behaviors) {
+            for (let name in cameraConfig.behaviors) {
+                this._setCameraBehavior(cameraConfig.behaviors[name], focusMeshes);
+            }
+        };
+
+        const sceneExtends = this.scene.getWorldExtends((mesh) => {
+            return !this.environmentHelper || (mesh !== this.environmentHelper.ground && mesh !== this.environmentHelper.rootMesh && mesh !== this.environmentHelper.skybox);
+        });
+        const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
+        const sceneDiagonalLenght = sceneDiagonal.length();
+        if (isFinite(sceneDiagonalLenght))
+            this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
+
+        this.onCameraConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.camera,
+            newConfiguration: cameraConfig,
+            model
+        });
+    }
+
+    protected _configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean, model?: ViewerModel) {
+        if (!skyboxConifguration && !groundConfiguration) {
+            if (this.environmentHelper) {
+                this.environmentHelper.dispose();
+                delete this.environmentHelper;
+            };
+            return Promise.resolve(this.scene);
+        }
+
+        const options: Partial<IEnvironmentHelperOptions> = {
+            createGround: !!groundConfiguration,
+            createSkybox: !!skyboxConifguration,
+            setupImageProcessing: false // will be done at the scene level!
+        };
+
+        if (groundConfiguration) {
+            let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
+
+            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;
+            if (groundConfig.texture) {
+                options.groundTexture = 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.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 = conf.cubeTexture.url;
+                } else {
+                    // init later!
+                    postInitSkyboxMaterial = true;
+                }
+            }
+
+            if (conf.material && conf.material.imageProcessingConfiguration) {
+                postInitSkyboxMaterial = true;
+            }
+        }
+
+        options.setupImageProcessing = false; // TMP
+
+        if (!this.environmentHelper) {
+            this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
+        } else {
+            // 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 (postInitSkyboxMaterial) {
+            let skyboxMaterial = this.environmentHelper.skyboxMaterial;
+            if (skyboxMaterial) {
+                if (typeof skyboxConifguration === 'object' && skyboxConifguration.material && skyboxConifguration.material.imageProcessingConfiguration) {
+                    extendClassWithConfig(skyboxMaterial.imageProcessingConfiguration, skyboxConifguration.material.imageProcessingConfiguration);
+                }
+            }
+        }
+
+        this.onEnvironmentConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.environmentHelper,
+            newConfiguration: {
+                skybox: skyboxConifguration,
+                ground: groundConfiguration
+            },
+            model
+        });
+    }
+
+    /**
+     * configure the lights.
+     * 
+     * @param lightsConfiguration the (new) light(s) configuration
+     * @param model optionally use the model to configure the camera.
+     */
+    protected _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
+        // sanity check!
+        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()
+                }
+            });
+        }
+
+        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 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;
+            }
+
+            //enabled
+            var enabled = lightConfig.enabled !== undefined ? lightConfig.enabled : !lightConfig.disabled;
+            light.setEnabled(enabled);
+
+
+            extendClassWithConfig(light, lightConfig);
+
+            //position. Some lights don't support shadows
+            if (light instanceof ShadowLight) {
+                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;
+                }
+                let shadowGenerator = light.getShadowGenerator();
+                if (lightConfig.shadowEnabled && this._maxShadows) {
+                    if (!shadowGenerator) {
+                        shadowGenerator = new ShadowGenerator(512, light);
+                        // TODO blur kernel definition
+                    }
+                    extendClassWithConfig(shadowGenerator, lightConfig.shadowConfig || {});
+                    // add the focues meshes to the shadow list
+                    let shadownMap = shadowGenerator.getShadowMap();
+                    if (!shadownMap) return;
+                    let renderList = shadownMap.renderList;
+                    for (var index = 0; index < focusMeshes.length; index++) {
+                        if (Tags.MatchesQuery(focusMeshes[index], 'castShadow')) {
+                            renderList && renderList.push(focusMeshes[index]);
+                        }
+                    }
+                } else if (shadowGenerator) {
+                    shadowGenerator.dispose();
+                }
+            }
+        });
+
+        this.onLightsConfiguredObservable.notifyObservers({
+            sceneManager: this,
+            object: this.scene.lights,
+            newConfiguration: lightsConfiguration,
+            model
+        });
+    }
+
+    /**
+     * Alters render settings to reduce features based on hardware feature limitations
+     * @param enableHDR Allows the viewer to run in HDR mode.
+     */
+    protected _handleHardwareLimitations(enableHDR = true) {
+        //flip rendering settings switches based on hardware support
+        let maxVaryingRows = this._viewer.engine.getCaps().maxVaryingVectors;
+        let maxFragmentSamplers = this._viewer.engine.getCaps().maxTexturesImageUnits;
+
+        //shadows are disabled if there's not enough varyings for a single shadow
+        if ((maxVaryingRows < 8) || (maxFragmentSamplers < 8)) {
+            this._maxShadows = 0;
+        } else {
+            this._maxShadows = 3;
+        }
+
+        //can we render to any >= 16-bit targets (required for HDR)
+        let caps = this._viewer.engine.getCaps();
+        let linearHalfFloatTargets = caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering;
+        let linearFloatTargets = caps.textureFloatRender && caps.textureFloatLinearFiltering;
+
+        this._hdrSupport = enableHDR && !!(linearFloatTargets || linearHalfFloatTargets);
+
+        if (linearHalfFloatTargets) {
+            this._defaultHighpTextureType = Engine.TEXTURETYPE_HALF_FLOAT;
+            this._shadowGeneratorBias = 0.002;
+        } else if (linearFloatTargets) {
+            this._defaultHighpTextureType = Engine.TEXTURETYPE_FLOAT;
+            this._shadowGeneratorBias = 0.001;
+        } else {
+            this._defaultHighpTextureType = Engine.TEXTURETYPE_UNSIGNED_INT;
+            this._shadowGeneratorBias = 0.001;
+        }
+
+        this._defaultPipelineTextureType = this._hdrSupport ? this._defaultHighpTextureType : Engine.TEXTURETYPE_UNSIGNED_INT;
+    }
+
+    /**
+     * Dispoe the entire viewer including the scene and the engine
+     */
+    public dispose() {
+
+        if (this.sceneOptimizer) {
+            this.sceneOptimizer.stop();
+            this.sceneOptimizer.dispose();
+        }
+
+        if (this.environmentHelper) {
+            this.environmentHelper.dispose();
+        }
+
+        this.models.forEach(model => {
+            model.dispose();
+        });
+
+        this.models.length = 0;
+
+        this.scene.dispose();
+    }
+
+    private _setCameraBehavior(behaviorConfig: number | {
+        type: number;
+        [propName: string]: any;
+    }, payload: any) {
+
+        let behavior: Behavior<ArcRotateCamera> | null;
+        let type = (typeof behaviorConfig !== "object") ? behaviorConfig : behaviorConfig.type;
+
+        let config: { [propName: string]: any } = (typeof behaviorConfig === "object") ? behaviorConfig : {};
+
+        // constructing behavior
+        switch (type) {
+            case CameraBehavior.AUTOROTATION:
+                this.camera.useAutoRotationBehavior = true;
+                behavior = this.camera.autoRotationBehavior;
+                break;
+            case CameraBehavior.BOUNCING:
+                this.camera.useBouncingBehavior = true;
+                behavior = this.camera.bouncingBehavior;
+                break;
+            case CameraBehavior.FRAMING:
+                this.camera.useFramingBehavior = true;
+                behavior = this.camera.framingBehavior;
+                break;
+            default:
+                behavior = null;
+                break;
+        }
+
+        if (behavior) {
+            if (typeof behaviorConfig === "object") {
+                extendClassWithConfig(behavior, behaviorConfig);
+            }
+        }
+
+        // post attach configuration. Some functionalities require the attached camera.
+        switch (type) {
+            case CameraBehavior.AUTOROTATION:
+                break;
+            case CameraBehavior.BOUNCING:
+                break;
+            case CameraBehavior.FRAMING:
+                if (config.zoomOnBoundingInfo) {
+                    //payload is an array of meshes
+                    let meshes = <Array<AbstractMesh>>payload;
+                    let bounding = meshes[0].getHierarchyBoundingVectors();
+                    (<FramingBehavior>behavior).zoomOnBoundingInfo(bounding.min, bounding.max);
+                }
+                break;
+        }
+    }
+}

+ 40 - 625
Viewer/src/viewer/viewer.ts

@@ -1,4 +1,5 @@
 import { viewerManager } from './viewerManager';
+import { SceneManager } from './sceneManager';
 import { TemplateManager } from './../templateManager';
 import { ConfigurationLoader } from './../configuration/loader';
 import { Skeleton, AnimationGroup, ParticleSystem, CubeTexture, Color3, IEnvironmentHelperOptions, EnvironmentHelper, Effect, SceneOptimizer, SceneOptimizerOptions, Observable, Engine, Scene, ArcRotateCamera, Vector3, SceneLoader, AbstractMesh, Mesh, HemisphericLight, Database, SceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync, Quaternion, Light, ShadowLight, ShadowGenerator, Tags, AutoRotationBehavior, BouncingBehavior, FramingBehavior, Behavior, Tools } from 'babylonjs';
@@ -9,7 +10,8 @@ import { ViewerModel } from '../model/viewerModel';
 import { GroupModelAnimation } from '../model/modelAnimation';
 import { ModelLoader } from '../model/modelLoader';
 import { CameraBehavior } from '../interfaces';
-import { viewerGlobals } from '..';
+import { viewerGlobals } from '../configuration/globals';
+import { extendClassWithConfig } from '../helper';
 
 /**
  * The AbstractViewr is the center of Babylon's viewer.
@@ -27,28 +29,13 @@ export abstract class AbstractViewer {
      */
     public engine: Engine;
     /**
-     * The Babylon Scene of this viewer
-     */
-    public scene: Scene;
-    /**
-     * The camera used in this viewer
-     */
-    public camera: ArcRotateCamera;
-    /**
-     * Babylon's scene optimizer
-     */
-    public sceneOptimizer: SceneOptimizer;
-    /**
      * The ID of this viewer. it will be generated randomly or use the HTML Element's ID.
      */
     public readonly baseId: string;
-    /**
-     * Models displayed in this viewer.
-     */
-    public models: Array<ViewerModel>;
 
     /**
      * The last loader used to load a model. 
+     * @deprecated
      */
     public lastUsedLoader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
     /**
@@ -56,43 +43,20 @@ export abstract class AbstractViewer {
      */
     public modelLoader: ModelLoader;
 
-    public runRenderLoop: boolean = true;
-
-    /**
-     * the viewer configuration object
-     */
-    protected _configuration: ViewerConfiguration;
-    /**
-     * Babylon's environment helper of this viewer
-     */
-    public environmentHelper: EnvironmentHelper;
-
-    //The following are configuration objects, default values.
-    protected _defaultHighpTextureType: number;
-    protected _shadowGeneratorBias: number;
-    protected _defaultPipelineTextureType: number;
-
     /**
-     * The maximum number of shadows supported by the curent viewer
+     * A flag that controls whether or not the render loop should be executed
      */
-    protected _maxShadows: number;
-    /**
-     * is HDR supported?
-     */
-    private _hdrSupport: boolean;
+    public runRenderLoop: boolean = true;
 
     /**
-     * is this viewer disposed?
+     * The scene manager connected with this viewer instance
      */
-    protected _isDisposed: boolean = false;
+    public sceneManager: SceneManager;
 
     /**
-     * Returns a boolean representing HDR support
+     * the viewer configuration object
      */
-    public get isHdrSupported() {
-        return this._hdrSupport;
-    }
-
+    protected _configuration: ViewerConfiguration;
 
     // observables
     /**
@@ -152,6 +116,12 @@ export abstract class AbstractViewer {
         return this._canvas;
     }
 
+
+    /**
+     * is this viewer disposed?
+     */
+    protected _isDisposed: boolean = false;
+
     /**
      * registered onBeforeRender functions.
      * This functions are also registered at the native scene. The reference can be used to unregister them.
@@ -182,7 +152,6 @@ export abstract class AbstractViewer {
         this.onFrameRenderedObservable = new Observable();
 
         this._registeredOnBeforeRenderFunctions = [];
-        this.models = [];
         this.modelLoader = new ModelLoader(this);
 
         // add this viewer to the viewer manager
@@ -190,6 +159,7 @@ export abstract class AbstractViewer {
 
         // create a new template manager. TODO - singleton?
         this.templateManager = new TemplateManager(containerElement);
+        this.sceneManager = new SceneManager(this);
 
         this._prepareContainerElement();
 
@@ -245,6 +215,15 @@ export abstract class AbstractViewer {
     }
 
     /**
+     * Get the configuration object. This is a reference only. 
+     * The configuration can ONLY be updated using the updateConfiguration function.
+     * changing this object will have no direct effect on the scene.
+     */
+    public get configuration(): ViewerConfiguration {
+        return this._configuration;
+    }
+
+    /**
      * force resizing the engine.
      */
     public forceResize() {
@@ -278,8 +257,8 @@ export abstract class AbstractViewer {
      * render loop that will be executed by the engine
      */
     protected _render = (force: boolean = false): void => {
-        if (force || (this.runRenderLoop && this.scene && this.scene.activeCamera)) {
-            this.scene.render();
+        if (force || (this.runRenderLoop && this.sceneManager.scene && this.sceneManager.scene.activeCamera)) {
+            this.sceneManager.scene.render();
             this.onFrameRenderedObservable.notifyObservers(this);
         }
     }
@@ -295,249 +274,12 @@ export abstract class AbstractViewer {
         // update this.configuration with the new data
         this._configuration = deepmerge(this._configuration || {}, newConfiguration);
 
-        // update scene configuration
-        if (newConfiguration.scene) {
-            this._configureScene(newConfiguration.scene);
-        }
-        // optimizer
-        if (newConfiguration.optimizer) {
-            this._configureOptimizer(newConfiguration.optimizer);
-        }
+        this.sceneManager.updateConfiguration(newConfiguration, this._configuration);
 
         // observers in configuration
         if (newConfiguration.observers) {
             this._configureObservers(newConfiguration.observers);
         }
-
-        // configure model
-        if (newConfiguration.model && typeof newConfiguration.model === 'object') {
-            this._configureModel(newConfiguration.model);
-        }
-
-        // lights
-        if (newConfiguration.lights) {
-            this._configureLights(newConfiguration.lights);
-        }
-
-        // environment
-        if (newConfiguration.skybox !== undefined || newConfiguration.ground !== undefined) {
-            this._configureEnvironment(newConfiguration.skybox, newConfiguration.ground);
-        }
-
-        // camera
-        if (newConfiguration.camera) {
-            this._configureCamera(newConfiguration.camera);
-        }
-    }
-
-    protected _configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean) {
-        if (!skyboxConifguration && !groundConfiguration) {
-            if (this.environmentHelper) {
-                this.environmentHelper.dispose();
-                delete this.environmentHelper;
-            };
-            return Promise.resolve(this.scene);
-        }
-
-        const options: Partial<IEnvironmentHelperOptions> = {
-            createGround: !!groundConfiguration,
-            createSkybox: !!skyboxConifguration,
-            setupImageProcessing: false // will be done at the scene level!
-        };
-
-        if (groundConfiguration) {
-            let groundConfig = (typeof groundConfiguration === 'boolean') ? {} : groundConfiguration;
-
-            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;
-            if (groundConfig.texture) {
-                options.groundTexture = 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.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 = conf.cubeTexture.url;
-                } else {
-                    // init later!
-                    postInitSkyboxMaterial = true;
-                }
-            }
-
-            if (conf.material && conf.material.imageProcessingConfiguration) {
-                postInitSkyboxMaterial = true;
-            }
-        }
-
-        options.setupImageProcessing = false; // TMP
-
-        if (!this.environmentHelper) {
-            this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
-        } else {
-            // 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 (postInitSkyboxMaterial) {
-            let skyboxMaterial = this.environmentHelper.skyboxMaterial;
-            if (skyboxMaterial) {
-                if (typeof skyboxConifguration === 'object' && skyboxConifguration.material && skyboxConifguration.material.imageProcessingConfiguration) {
-                    this._extendClassWithConfig(skyboxMaterial.imageProcessingConfiguration, skyboxConifguration.material.imageProcessingConfiguration);
-                }
-            }
-        }
-    }
-
-    /**
-     * internally configure the scene using the provided configuration.
-     * The scene will not be recreated, but just updated.
-     * @param sceneConfig the (new) scene configuration
-     */
-    protected _configureScene(sceneConfig: ISceneConfiguration) {
-        // sanity check!
-        if (!this.scene) {
-            return;
-        }
-        if (sceneConfig.debug) {
-            this.scene.debugLayer.show();
-        } else {
-            if (this.scene.debugLayer.isVisible()) {
-                this.scene.debugLayer.hide();
-            }
-        }
-
-        if (sceneConfig.clearColor) {
-            let cc = sceneConfig.clearColor;
-            let oldcc = this.scene.clearColor;
-            if (cc.r !== undefined) {
-                oldcc.r = cc.r;
-            }
-            if (cc.g !== undefined) {
-                oldcc.g = cc.g
-            }
-            if (cc.b !== undefined) {
-                oldcc.b = cc.b
-            }
-            if (cc.a !== undefined) {
-                oldcc.a = cc.a
-            }
-        }
-
-        // image processing configuration - optional.
-        if (sceneConfig.imageProcessingConfiguration) {
-            this._extendClassWithConfig(this.scene.imageProcessingConfiguration, sceneConfig.imageProcessingConfiguration);
-        }
-        if (sceneConfig.environmentTexture) {
-            if (this.scene.environmentTexture) {
-                this.scene.environmentTexture.dispose();
-            }
-            const environmentTexture = CubeTexture.CreateFromPrefilteredData(sceneConfig.environmentTexture, this.scene);
-            this.scene.environmentTexture = environmentTexture;
-        }
-
-        if (sceneConfig.autoRotate) {
-            this.camera.useAutoRotationBehavior = true;
-        }
-    }
-
-
-    /**
-     * Configure the scene optimizer.
-     * The existing scene optimizer will be disposed and a new one will be created.
-     * @param optimizerConfig the (new) optimizer configuration
-     */
-    protected _configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean) {
-        if (typeof optimizerConfig === 'boolean') {
-            if (this.sceneOptimizer) {
-                this.sceneOptimizer.stop();
-                this.sceneOptimizer.dispose();
-                delete this.sceneOptimizer;
-            }
-            if (optimizerConfig) {
-                this.sceneOptimizer = new SceneOptimizer(this.scene);
-                this.sceneOptimizer.start();
-            }
-        } else {
-            let optimizerOptions: SceneOptimizerOptions = new SceneOptimizerOptions(optimizerConfig.targetFrameRate, optimizerConfig.trackerDuration);
-            // check for degradation
-            if (optimizerConfig.degradation) {
-                switch (optimizerConfig.degradation) {
-                    case "low":
-                        optimizerOptions = SceneOptimizerOptions.LowDegradationAllowed(optimizerConfig.targetFrameRate);
-                        break;
-                    case "moderate":
-                        optimizerOptions = SceneOptimizerOptions.ModerateDegradationAllowed(optimizerConfig.targetFrameRate);
-                        break;
-                    case "hight":
-                        optimizerOptions = SceneOptimizerOptions.HighDegradationAllowed(optimizerConfig.targetFrameRate);
-                        break;
-                }
-            }
-            if (this.sceneOptimizer) {
-                this.sceneOptimizer.stop();
-                this.sceneOptimizer.dispose()
-            }
-            this.sceneOptimizer = new SceneOptimizer(this.scene, optimizerOptions, optimizerConfig.autoGeneratePriorities, optimizerConfig.improvementMode);
-            this.sceneOptimizer.start();
-        }
     }
 
     /**
@@ -570,151 +312,6 @@ export abstract class AbstractViewer {
     }
 
     /**
-     * (Re) configure the camera. The camera will only be created once and from this point will only be reconfigured.
-     * @param cameraConfig the new camera configuration
-     * @param model optionally use the model to configure the camera.
-     */
-    protected _configureCamera(cameraConfig: ICameraConfiguration = {}, model?: ViewerModel) {
-        let focusMeshes = model ? model.meshes : this.scene.meshes;
-
-        if (!this.scene.activeCamera) {
-            this.scene.createDefaultCamera(true, true, true);
-            this.camera = <ArcRotateCamera>this.scene.activeCamera!;
-        }
-        if (cameraConfig.position) {
-            this.camera.position.copyFromFloats(cameraConfig.position.x || 0, cameraConfig.position.y || 0, cameraConfig.position.z || 0);
-        }
-
-        if (cameraConfig.rotation) {
-            this.camera.rotationQuaternion = new Quaternion(cameraConfig.rotation.x || 0, cameraConfig.rotation.y || 0, cameraConfig.rotation.z || 0, cameraConfig.rotation.w || 0)
-        }
-
-        this._extendClassWithConfig(this.camera, cameraConfig);
-
-        this.camera.minZ = cameraConfig.minZ || this.camera.minZ;
-        this.camera.maxZ = cameraConfig.maxZ || this.camera.maxZ;
-
-        if (cameraConfig.behaviors) {
-            for (let name in cameraConfig.behaviors) {
-                this._setCameraBehavior(cameraConfig.behaviors[name], focusMeshes);
-            }
-        };
-
-        const sceneExtends = this.scene.getWorldExtends((mesh) => {
-            return !this.environmentHelper || (mesh !== this.environmentHelper.ground && mesh !== this.environmentHelper.rootMesh && mesh !== this.environmentHelper.skybox);
-        });
-        const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
-        const sceneDiagonalLenght = sceneDiagonal.length();
-        if (isFinite(sceneDiagonalLenght))
-            this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
-    }
-
-    /**
-     * configure the lights.
-     * 
-     * @param lightsConfiguration the (new) light(s) configuration
-     * @param model optionally use the model to configure the camera.
-     */
-    protected _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
-        let focusMeshes = model ? model.meshes : this.scene.meshes;
-        // sanity check!
-        if (!Object.keys(lightsConfiguration).length) 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._configuration.lights || []);
-        if (Object.keys(lightsToConfigure).length !== lightsAvailable.length) {
-            lightsAvailable.forEach(lName => {
-                if (lightsToConfigure.indexOf(lName) === -1) {
-                    this.scene.getLightByName(lName)!.dispose()
-                }
-            });
-        }
-
-        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 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;
-            }
-
-            //enabled
-            var enabled = lightConfig.enabled !== undefined ? lightConfig.enabled : !lightConfig.disabled;
-            light.setEnabled(enabled);
-
-
-            this._extendClassWithConfig(light, lightConfig);
-
-            //position. Some lights don't support shadows
-            if (light instanceof ShadowLight) {
-                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;
-                }
-                let shadowGenerator = light.getShadowGenerator();
-                if (lightConfig.shadowEnabled && this._maxShadows) {
-                    if (!shadowGenerator) {
-                        shadowGenerator = new ShadowGenerator(512, light);
-                        // TODO blur kernel definition
-                    }
-                    this._extendClassWithConfig(shadowGenerator, lightConfig.shadowConfig || {});
-                    // add the focues meshes to the shadow list
-                    let shadownMap = shadowGenerator.getShadowMap();
-                    if (!shadownMap) return;
-                    let renderList = shadownMap.renderList;
-                    for (var index = 0; index < focusMeshes.length; index++) {
-                        if (Tags.MatchesQuery(focusMeshes[index], 'castShadow')) {
-                            renderList && renderList.push(focusMeshes[index]);
-                        }
-                    }
-                } else if (shadowGenerator) {
-                    shadowGenerator.dispose();
-                }
-            }
-        });
-    }
-
-    /**
-     * configure all models using the configuration.
-     * @param modelConfiguration the configuration to use to reconfigure the models
-     */
-    protected _configureModel(modelConfiguration: Partial<IModelConfiguration>) {
-        this.models.forEach(model => {
-            model.updateConfiguration(modelConfiguration);
-        })
-    }
-
-    /**
      * Dispoe the entire viewer including the scene and the engine
      */
     public dispose() {
@@ -722,18 +319,6 @@ export abstract class AbstractViewer {
             return;
         }
         window.removeEventListener('resize', this._resize);
-        if (this.sceneOptimizer) {
-            this.sceneOptimizer.stop();
-            this.sceneOptimizer.dispose();
-        }
-
-        if (this.environmentHelper) {
-            this.environmentHelper.dispose();
-        }
-
-        if (this._configurationLoader) {
-            this._configurationLoader.dispose();
-        }
 
         //observers
         this.onEngineInitObservable.clear();
@@ -753,19 +338,15 @@ export abstract class AbstractViewer {
         this.onFrameRenderedObservable.clear();
         delete this.onFrameRenderedObservable;
 
-        if (this.scene.activeCamera) {
-            this.scene.activeCamera.detachControl(this.canvas);
+        if (this.sceneManager.scene.activeCamera) {
+            this.sceneManager.scene.activeCamera.detachControl(this.canvas);
         }
 
-        this.modelLoader.dispose();
 
-        this.models.forEach(model => {
-            model.dispose();
-        });
+        this.sceneManager.dispose();
 
-        this.models.length = 0;
+        this.modelLoader.dispose();
 
-        this.scene.dispose();
         this.engine.dispose();
 
         this.templateManager.dispose();
@@ -800,9 +381,9 @@ export abstract class AbstractViewer {
                 return this.onEngineInitObservable.notifyObserversWithPromise(engine);
             }).then(() => {
                 if (modelConfiguration) {
-                    return this.loadModel(modelConfiguration).catch(e => { }).then(() => { return this.scene });
+                    return this.loadModel(modelConfiguration).catch(e => { }).then(() => { return this.sceneManager.scene });
                 } else {
-                    return this.scene || this._initScene();
+                    return this.sceneManager.scene || this.sceneManager.initScene(this._configuration.scene);
                 }
             }).then((scene) => {
                 return this.onSceneInitObservable.notifyObserversWithPromise(scene);
@@ -857,39 +438,9 @@ export abstract class AbstractViewer {
             this.engine.setHardwareScalingLevel(scale);
         }
 
-        // set hardware limitations for scene initialization
-        this._handleHardwareLimitations();
-
         return Promise.resolve(this.engine);
     }
 
-    /**
-     * initialize the scene. Calling thsi function again will dispose the old scene, if exists.
-     */
-    protected _initScene(): Promise<Scene> {
-
-        // if the scen exists, dispose it.
-        if (this.scene) {
-            this.scene.dispose();
-        }
-
-        // create a new scene
-        this.scene = new Scene(this.engine);
-        // make sure there is a default camera and light.
-        this.scene.createDefaultLight(true);
-
-        if (this._configuration.scene) {
-            this._configureScene(this._configuration.scene);
-
-            // Scene optimizer
-            if (this._configuration.optimizer) {
-                this._configureOptimizer(this._configuration.optimizer);
-            }
-        }
-
-        return Promise.resolve(this.scene);
-    }
-
     private _isLoading: boolean;
     private _nextLoading: Function;
 
@@ -907,8 +458,7 @@ export abstract class AbstractViewer {
             throw new Error("no model configuration provided");
         }
         if (clearScene) {
-            this.models.forEach(m => m.dispose());
-            this.models.length = 0;
+
         }
         let configuration: IModelConfiguration;
         if (typeof modelConfig === 'string') {
@@ -940,20 +490,10 @@ export abstract class AbstractViewer {
         this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
 
         model.onLoadedObservable.add(() => {
-            Promise.resolve().then(() => {
-                this._configureLights(this._configuration.lights, model);
-
-                if (this._configuration.camera || !this.scene.activeCamera) {
-                    this._configureCamera(this._configuration.camera || {}, model);
-                }
-                return this._initEnvironment(model);
-            }).then(() => {
-                this._isLoading = false;
-                return this.onModelLoadedObservable.notifyObserversWithPromise(model);
-            });
+            this._isLoading = false;
+            return this.onModelLoadedObservable.notifyObserversWithPromise(model);
         });
 
-
         return model;
     }
 
@@ -970,8 +510,8 @@ export abstract class AbstractViewer {
             return Promise.reject("another model is curently being loaded.");
         }
 
-        return Promise.resolve(this.scene).then((scene) => {
-            if (!scene) return this._initScene();
+        return Promise.resolve(this.sceneManager.scene).then((scene) => {
+            if (!scene) return this.sceneManager.initScene(this._configuration.scene, this._configuration.optimizer);
             return scene;
         }).then(() => {
             let model = this.initModel(modelConfig, clearScene);
@@ -988,55 +528,6 @@ export abstract class AbstractViewer {
     }
 
     /**
-     * initialize the environment for a specific model.
-     * Per default it will use the viewer'S configuration.
-     * @param model the model to use to configure the environment.
-     * @returns a Promise that will resolve when the configuration is done.
-     */
-    protected _initEnvironment(model?: ViewerModel): Promise<Scene> {
-        this._configureEnvironment(this._configuration.skybox, this._configuration.ground);
-
-        return Promise.resolve(this.scene);
-    }
-
-    /**
-     * Alters render settings to reduce features based on hardware feature limitations
-     * @param enableHDR Allows the viewer to run in HDR mode.
-     */
-    protected _handleHardwareLimitations(enableHDR = true) {
-        //flip rendering settings switches based on hardware support
-        let maxVaryingRows = this.engine.getCaps().maxVaryingVectors;
-        let maxFragmentSamplers = this.engine.getCaps().maxTexturesImageUnits;
-
-        //shadows are disabled if there's not enough varyings for a single shadow
-        if ((maxVaryingRows < 8) || (maxFragmentSamplers < 8)) {
-            this._maxShadows = 0;
-        } else {
-            this._maxShadows = 3;
-        }
-
-        //can we render to any >= 16-bit targets (required for HDR)
-        let caps = this.engine.getCaps();
-        let linearHalfFloatTargets = caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering;
-        let linearFloatTargets = caps.textureFloatRender && caps.textureFloatLinearFiltering;
-
-        this._hdrSupport = enableHDR && !!(linearFloatTargets || linearHalfFloatTargets);
-
-        if (linearHalfFloatTargets) {
-            this._defaultHighpTextureType = Engine.TEXTURETYPE_HALF_FLOAT;
-            this._shadowGeneratorBias = 0.002;
-        } else if (linearFloatTargets) {
-            this._defaultHighpTextureType = Engine.TEXTURETYPE_FLOAT;
-            this._shadowGeneratorBias = 0.001;
-        } else {
-            this._defaultHighpTextureType = Engine.TEXTURETYPE_UNSIGNED_INT;
-            this._shadowGeneratorBias = 0.001;
-        }
-
-        this._defaultPipelineTextureType = this._hdrSupport ? this._defaultHighpTextureType : Engine.TEXTURETYPE_UNSIGNED_INT;
-    }
-
-    /**
      * Injects all the spectre shader in the babylon shader store
      */
     protected _injectCustomShaders(): void {
@@ -1058,80 +549,4 @@ export abstract class AbstractViewer {
             });
         }
     }
-
-    /**
-     * This will extend an object with configuration values.
-     * What it practically does it take the keys from the configuration and set them on the object.
-     * I the configuration is a tree, it will traverse into the tree.
-     * @param object the object to extend
-     * @param config the configuration object that will extend the object
-     */
-    protected _extendClassWithConfig(object: any, config: any) {
-        if (!config) return;
-        Object.keys(config).forEach(key => {
-            if (key in object && typeof object[key] !== 'function') {
-                // if (typeof object[key] === 'function') return;
-                // if it is an object, iterate internally until reaching basic types
-                if (typeof object[key] === 'object') {
-                    this._extendClassWithConfig(object[key], config[key]);
-                } else {
-                    if (config[key] !== undefined) {
-                        object[key] = config[key];
-                    }
-                }
-            }
-        });
-    }
-
-    private _setCameraBehavior(behaviorConfig: number | {
-        type: number;
-        [propName: string]: any;
-    }, payload: any) {
-
-        let behavior: Behavior<ArcRotateCamera> | null;
-        let type = (typeof behaviorConfig !== "object") ? behaviorConfig : behaviorConfig.type;
-
-        let config: { [propName: string]: any } = (typeof behaviorConfig === "object") ? behaviorConfig : {};
-
-        // constructing behavior
-        switch (type) {
-            case CameraBehavior.AUTOROTATION:
-                this.camera.useAutoRotationBehavior = true;
-                behavior = this.camera.autoRotationBehavior;
-                break;
-            case CameraBehavior.BOUNCING:
-                this.camera.useBouncingBehavior = true;
-                behavior = this.camera.bouncingBehavior;
-                break;
-            case CameraBehavior.FRAMING:
-                this.camera.useFramingBehavior = true;
-                behavior = this.camera.framingBehavior;
-                break;
-            default:
-                behavior = null;
-                break;
-        }
-
-        if (behavior) {
-            if (typeof behaviorConfig === "object") {
-                this._extendClassWithConfig(behavior, behaviorConfig);
-            }
-        }
-
-        // post attach configuration. Some functionalities require the attached camera.
-        switch (type) {
-            case CameraBehavior.AUTOROTATION:
-                break;
-            case CameraBehavior.BOUNCING:
-                break;
-            case CameraBehavior.FRAMING:
-                if (config.zoomOnBoundingInfo) {
-                    //payload is an array of meshes
-                    let meshes = <Array<AbstractMesh>>payload;
-                    let bounding = meshes[0].getHierarchyBoundingVectors();
-                    (<FramingBehavior>behavior).zoomOnBoundingInfo(bounding.min, bounding.max);
-                }
-                break;
-        }
-    }
 }

+ 1 - 1
Viewer/tests/unit/src/Viewer/viewer.ts

@@ -56,7 +56,7 @@ describe('Viewer', function () {
         viewer.onSceneInitObservable.add(() => {
             // force-create a camera for the render loop to work
             viewer.updateConfiguration({ camera: {} });
-            viewer.scene.registerAfterRender(() => {
+            viewer.sceneManager.scene.registerAfterRender(() => {
                 sceneRenderCount++;
             })
         });