浏览代码

Merge pull request #4221 from RaananW/viewer-extends

Viewer extends
Raanan Weber 7 年之前
父节点
当前提交
d6fe6fab70

+ 1 - 1
Tools/Gulp/config.json

@@ -1764,7 +1764,7 @@
                 "name": "babylonjs-viewer",
                 "main": "../../Viewer/src/index.d.ts",
                 "out": "../../dist/preview release/viewer/babylon.viewer.module.d.ts",
-                "prependText": "/// <reference path=\"./babylon.d.ts\"/>\n/// <reference path=\"./babylon.glTF2Interface.d.ts\"/>\n/// <reference path=\"./babylonjs.loaders.d.ts\"/>\n"
+                "prependText": "/// <reference path=\"./babylon.d.ts\"/>\n/// <reference path=\"./babylon.glTF2Interface.d.ts\"/>\n/// <reference path=\"./babylonjs.loaders.d.ts\"/>\ndeclare module \"babylonjs-loaders\"{ export=BABYLON;}\n"
             },
             "outputs": [
                 {

+ 3 - 3
Viewer/assets/templates/default/loadingScreen.html

@@ -10,9 +10,9 @@
         display: flex;
         justify-content: center;
         align-items: center;
-        -webkit-transition: opacity 2s ease;
-        -moz-transition: opacity 2s ease;
-        transition: opacity 2s ease;
+        -webkit-transition: opacity 1s ease;
+        -moz-transition: opacity 1s ease;
+        transition: opacity 1s ease;
     }
 
     img.loading-image {

二进制
Viewer/dist/assets/environment/Skybox_2.0-256.dds


+ 3 - 1
Viewer/dist/ufoExample.html

@@ -20,8 +20,10 @@
     <body>
         <babylon extends="default, shadowDirectionalLight, environmentMap" templates.nav-bar.params.hide-animations="true" templates.nav-bar.params.disable-on-fullscreen="true">
             <scene glow="true">
-                <main-color r="0.5" g="0.2" b="0.2"></main-color>
             </scene>
+            <lab>
+                <environment-main-color r="0.5" g="0.2" b="0.2"></environment-main-color>
+            </lab>
             <model url="https://models.babylonjs.com/ufo.glb">
                 <animation auto-start="true"></animation>
             </model>

+ 76 - 19
Viewer/src/configuration/configuration.ts

@@ -1,5 +1,19 @@
 import { ITemplateConfiguration } from './../templateManager';
-import { EngineOptions, IGlowLayerOptions } from 'babylonjs';
+import { EngineOptions, IGlowLayerOptions, DepthOfFieldEffectBlurLevel } from 'babylonjs';
+
+export function getConfigurationKey(key: string, configObject: any) {
+    let splits = key.split('.');
+
+    if (splits.length === 0 || !configObject) return false;
+    else if (splits.length === 1) {
+        if (configObject[key] !== undefined) {
+            return configObject[key];
+        }
+    } else {
+        let firstKey = splits.shift();
+        return getConfigurationKey(splits.join("."), configObject[firstKey!])
+    }
+}
 
 export interface ViewerConfiguration {
 
@@ -32,6 +46,7 @@ export interface ViewerConfiguration {
     lights?: { [name: string]: boolean | ILightConfiguration },
     // engine configuration. optional!
     engine?: {
+        renderInBackground?: boolean;
         antialiasing?: boolean;
         disableResize?: boolean;
         engineOptions?: EngineOptions;
@@ -73,7 +88,8 @@ export interface ViewerConfiguration {
             specular?: { r: number, g: number, b: number };
         }
         hideLoadingDelay?: number;
-        environmentAssetsRootURL?: string;
+        assetsRootURL?: string;
+        environmentMainColor?: { r: number, g: number, b: number };
         environmentMap?: {
             /**
              * Environment map texture path in relative to the asset folder.
@@ -90,30 +106,67 @@ export interface ViewerConfiguration {
              */
             tintLevel: number;
         }
-        renderingPipelines?: {
-            default?: boolean | {
-                [propName: string]: any;
-            };
-            standard?: boolean | {
-                [propName: string]: any;
-            };
-            /*lens?: boolean | {
-                [propName: string]: boolean | string | number | undefined;
-            };*/
-            ssao?: boolean | {
-                [propName: string]: any;
-            };
-            ssao2?: boolean | {
-                [propName: string]: any;
-            };
-        }
+        defaultRenderingPipelines?: boolean | IDefaultRenderingPipelineConfiguration;
     }
 }
 
+/**
+ * Defines an animation to be applied to a model (translation, scale or rotation).
+ */
+export interface IModelAnimationConfiguration {
+    /**
+     * Time of animation, in seconds
+     */
+    time?: number;
+
+    /**
+     * Scale to apply
+     */
+    scaling?: {
+        x: number;
+        y: number;
+        z: number;
+    };
+
+    /**
+     * Easing function to apply
+     * See SPECTRE.EasingFunction
+     */
+    easingFunction?: number;
+
+    /**
+     * An Easing mode to apply to the easing function
+     * See BABYLON.EasingFunction
+     */
+    easingMode?: number;
+}
+
+
+export interface IDefaultRenderingPipelineConfiguration {
+    sharpenEnabled?: boolean;
+    bloomEnabled?: boolean;
+    bloomThreshold?: number;
+    depthOfFieldEnabled?: boolean;
+    depthOfFieldBlurLevel?: DepthOfFieldEffectBlurLevel;
+    fxaaEnabled?: boolean;
+    imageProcessingEnabled?: boolean;
+    defaultPipelineTextureType?: number;
+    bloomScale?: number;
+    chromaticAberrationEnabled?: boolean;
+    grainEnabled?: boolean;
+    bloomKernel?: number;
+    hardwareScaleLevel?: number;
+    bloomWeight?: number;
+    bllomThreshold?: number;
+    hdr?: boolean;
+    samples?: number;
+}
+
 export interface IModelConfiguration {
     id?: string;
     url?: string;
     root?: string; //optional
+    file?: string; // is a file being loaded? root and url ignored
     loader?: string; // obj, gltf?
     position?: { x: number, y: number, z: number };
     rotation?: { x: number, y: number, z: number, w?: number };
@@ -137,6 +190,9 @@ export interface IModelConfiguration {
         playOnce?: boolean;
     }
 
+    entryAnimation?: IModelAnimationConfiguration;
+    exitAnimation?: IModelAnimationConfiguration;
+
     material?: {
         directEnabled?: boolean;
         directIntensity?: number;
@@ -358,6 +414,7 @@ export interface ISceneOptimizerConfiguration {
         renderTarget?: ISceneOptimizerParameters;
         mergeMeshes?: ISceneOptimizerParameters;
     }
+    custom?: string;
 }
 
 export interface IObserversConfiguration {

+ 10 - 6
Viewer/src/configuration/types/default.ts

@@ -62,13 +62,17 @@ export let defaultConfiguration: ViewerConfiguration = {
     },
     camera: {
         behaviors: {
-            autoRotate: 0,
+            autoRotate: {
+                type: 0
+            },
             framing: {
                 type: 2,
                 zoomOnBoundingInfo: true,
                 zoomStopsAnimation: false
             },
-            bouncing: 1
+            bouncing: {
+                type: 1
+            }
         },
         wheelPrecision: 200,
     },
@@ -77,9 +81,9 @@ export let defaultConfiguration: ViewerConfiguration = {
             url: 'https://playground.babylonjs.com/textures/environment.dds',
             gammaSpace: false
         },*/
-        pbr: true,
+        /*pbr: true,
         blur: 0.7,
-        infiniteDistance: false,
+        infiniteDistance: false,*/
         /*material: {
             imageProcessingConfiguration: {
                 colorCurves: {
@@ -105,10 +109,10 @@ export let defaultConfiguration: ViewerConfiguration = {
         antialiasing: true
     },
     scene: {
-        imageProcessingConfiguration: {
+        /*imageProcessingConfiguration: {
             exposure: 1.4,
             contrast: 1.66,
             toneMappingEnabled: true
-        }
+        }*/
     }
 }

+ 1 - 1
Viewer/src/configuration/types/environmentMap.ts

@@ -2,7 +2,7 @@ import { ViewerConfiguration } from './../configuration';
 
 export const environmentMapConfiguration: ViewerConfiguration = {
     lab: {
-        environmentAssetsRootURL: '/assets/environment/',
+        assetsRootURL: '/assets/environment/',
         environmentMap: {
             texture: 'EnvMap_2.0-256.env',
             rotationY: 0,

+ 259 - 38
Viewer/src/configuration/types/extended.ts

@@ -1,61 +1,257 @@
 import { ViewerConfiguration } from './../configuration';
+import { Tools } from 'babylonjs';
 
 export let extendedConfiguration: ViewerConfiguration = {
     version: "3.2.0",
     extends: "default",
     camera: {
+        exposure: 3.034578,
+        fov: 0.7853981633974483,
+        contrast: 1.6,
+        toneMappingEnabled: true,
+        upperBetaLimit: 1.3962634015954636 + Math.PI / 2,
+        lowerBetaLimit: -1.4835298641951802 + Math.PI / 2,
+        behaviors: {
+            framing: {
+                type: 2,
+                mode: 0,
+                positionScale: 0.5,
+                defaultElevation: 0.2617993877991494,
+                elevationReturnWaitTime: 3000,
+                elevationReturnTime: 2000,
+                framingTime: 500,
+                zoomStopsAnimation: false,
+                radiusScale: 0.866
+            },
+            autoRotate: {
+                type: 0,
+                idleRotationWaitTime: 4000,
+                idleRotationSpeed: 0.17453292519943295,
+                idleRotationSpinupTime: 2500,
+                zoomStopsAnimation: false
+            },
+            bouncing: {
+                type: 1,
+                lowerRadiusTransitionRange: 0.05,
+                upperRadiusTransitionRange: -0.2
+            }
+        },
+        upperRadiusLimit: 5,
+        lowerRadiusLimit: 0.5,
+        frameOnModelLoad: true,
+        framingElevation: 0.2617993877991494,
+        framingRotation: 1.5707963267948966,
         radius: 2,
-        alpha: -1.5708,
+        alpha: 1.5708,
         beta: Math.PI * 0.5 - 0.2618,
         wheelPrecision: 300,
         minZ: 0.1,
         maxZ: 50,
+        fovMode: 0,
+        pinchPrecision: 1500,
+        panningSensibility: 3000
     },
     lights: {
-        "light1": {
+        light0: {
             type: 0,
-            shadowEnabled: false,
-            position: { x: -1.78, y: 2.298, z: 2.62 },
-            diffuse: { r: 0.8, g: 0.8, b: 0.8 },
-            intensity: 3,
+            frustumEdgeFalloff: 0,
+            intensity: 7,
             intensityMode: 0,
-            radius: 3.135,
+            radius: 0.6,
+            range: 0.6,
+            spotAngle: 60,
+            diffuse: {
+                r: 1,
+                g: 1,
+                b: 1
+            },
+            position: {
+                x: -2,
+                y: 2.5,
+                z: 2
+            },
+            target: {
+                x: 0,
+                y: 0,
+                z: 0
+            },
+            enabled: true,
+            shadowEnabled: true,
+            shadowBufferSize: 512,
+            shadowMinZ: 1,
+            shadowMaxZ: 10,
+            shadowFieldOfView: 60,
+            shadowFrustumSize: 2,
+            shadowConfig: {
+                useBlurCloseExponentialShadowMap: true,
+                useKernelBlur: true,
+                blurScale: 1.0,
+                bias: 0.001,
+                depthScale: 50 * (10 - 1),
+                frustumEdgeFalloff: 0
+            }
         },
-        "light3": {
-            type: 2,
+        light1: {
+            type: 0,
+            frustumEdgeFalloff: 0,
+            intensity: 7,
+            intensityMode: 0,
+            radius: 0.4,
+            range: 0.4,
+            spotAngle: 57,
+            diffuse: {
+                r: 1,
+                g: 1,
+                b: 1
+            },
+            position: {
+                x: 4,
+                y: 3,
+                z: -0.5
+            },
+            target: {
+                x: 0,
+                y: 0,
+                z: 0
+            },
+            enabled: true,
             shadowEnabled: false,
-            position: { x: -4, y: 2, z: -2.23 },
-            diffuse: { r: 0.718, g: 0.772, b: 0.749 },
-            intensity: 2.052,
+            shadowBufferSize: 512,
+            shadowMinZ: 0.2,
+            shadowMaxZ: 10,
+            shadowFieldOfView: 28,
+            shadowFrustumSize: 2
+        },
+        light2: {
+            type: 0,
+            frustumEdgeFalloff: 0,
+            intensity: 1,
             intensityMode: 0,
             radius: 0.5,
-            spotAngle: 42.85
+            range: 0.5,
+            spotAngle: 42.85,
+            diffuse: {
+                r: 0.8,
+                g: 0.8,
+                b: 0.8
+            },
+            position: {
+                x: -1,
+                y: 3,
+                z: -3
+            },
+            target: {
+                x: 0,
+                y: 0,
+                z: 0
+            },
+            enabled: true,
+            shadowEnabled: false,
+            shadowBufferSize: 512,
+            shadowMinZ: 0.2,
+            shadowMaxZ: 10,
+            shadowFieldOfView: 45,
+            shadowFrustumSize: 2
         }
     },
     ground: {
-        receiveShadows: true
+        shadowLevel: 0.9,
+        texture: "Ground_2.0-1024.png",
+        material: {
+            primaryColorHighlightLevel: 0.035,
+            primaryColorShadowLevel: 0,
+            enableNoise: true,
+            useRGBColor: false,
+            maxSimultaneousLights: 1,
+            diffuseTexture: {
+                gammaSpace: true
+            }
+        },
+        opacity: 1,
+        mirror: false,
+        receiveShadows: true,
+        size: 5
+    },
+    skybox: {
+        scale: 11,
+        cubeTexture: {
+            url: "Skybox_2.0-256.dds"
+        },
+        material: {
+            primaryColorHighlightLevel: 0.03,
+            primaryColorShadowLevel: 0.03,
+            enableNoise: true,
+            useRGBColor: false,
+            reflectionTexture: {
+                gammaSpace: true
+            }
+        }
+    },
+    engine: {
+        renderInBackground: true
     },
     scene: {
+        flags: {
+            shadowsEnabled: true,
+            particlesEnabled: false,
+            collisionsEnabled: false,
+            lightsEnabled: true,
+            texturesEnabled: true,
+            lensFlaresEnabled: false,
+            proceduralTexturesEnabled: false,
+            renderTargetsEnabled: true,
+            spritesEnabled: false,
+            skeletonsEnabled: true,
+            audioEnabled: false,
+        },
+        defaultMaterial: {
+            materialType: 'pbr',
+            reflectivityColor: {
+                r: 0.1,
+                g: 0.1,
+                b: 0.1
+            },
+            microSurface: 0.6
+        },
+        clearColor: {
+            r: 0.9,
+            g: 0.9,
+            b: 0.9,
+            a: 1.0
+        },
         imageProcessingConfiguration: {
+            vignetteCentreX: 0,
+            vignetteCentreY: 0,
+            vignetteColor: {
+                r: 0.086,
+                g: 0.184,
+                b: 0.259,
+                a: 1
+            },
+            vignetteWeight: 0.855,
+            vignetteStretch: 0.5,
+            vignetteBlendMode: 0,
+            vignetteCameraFov: 0.7853981633974483,
+            isEnabled: true,
             colorCurves: {
-                shadowsHue: 43.359,
-                shadowsDensity: 1,
-                shadowsSaturation: -25,
-                shadowsExposure: -3.0,
-                midtonesHue: 93.65,
-                midtonesDensity: -15.24,
-                midtonesExposure: 7.37,
-                midtonesSaturation: -15,
-                highlightsHue: 37.2,
-                highlightsDensity: -22.43,
-                highlightsExposure: 45.0,
-                highlightsSaturation: -15
+                shadowsHue: 0,
+                shadowsDensity: 0,
+                shadowsSaturation: 0,
+                shadowsExposure: 0,
+                midtonesHue: 0,
+                midtonesDensity: 0,
+                midtonesExposure: 0,
+                midtonesSaturation: 0,
+                highlightsHue: 0,
+                highlightsDensity: 0,
+                highlightsExposure: 0,
+                highlightsSaturation: 0
             }
         },
         mainColor: {
-            r: 0.7,
-            g: 0.7,
-            b: 0.7
+            r: 0.8823529411764706,
+            g: 0.8823529411764706,
+            b: 0.8823529411764706
         }
     },
     loaderPlugins: {
@@ -67,27 +263,52 @@ export let extendedConfiguration: ViewerConfiguration = {
     model: {
         rotationOffsetAxis: {
             x: 0,
-            y: 1,
+            y: -1,
             z: 0
         },
-        rotationOffsetAngle: 3.66519,
+        rotationOffsetAngle: Tools.ToRadians(210),
         material: {
             directEnabled: true,
             directIntensity: 0.884,
             emissiveIntensity: 1.04,
-            environmentIntensity: 0.868
+            environmentIntensity: 0.6
+        },
+        entryAnimation: {
+            scaling: {
+                x: 0,
+                y: 0,
+                z: 0
+            },
+            time: 0.5,
+            easingFunction: 4,
+            easingMode: 1
+        },
+        exitAnimation: {
+            scaling: {
+                x: 0,
+                y: 0,
+                z: 0
+            },
+            time: 0.5,
+            easingFunction: 4,
+            easingMode: 1
         },
         normalize: true,
         castShadow: true,
         receiveShadows: true
     },
     lab: {
-        renderingPipelines: {
-            default: {
-                bloomEnabled: true,
-                bloomThreshold: 1.0,
-                fxaaEnabled: true
-            }
+        assetsRootURL: '/assets/environment/',
+        environmentMap: {
+            texture: "EnvMap_2.0-256.env",
+            rotationY: 3,
+            tintLevel: 0.4
+        },
+        defaultRenderingPipelines: {
+            bloomEnabled: true,
+            bloomThreshold: 1.0,
+            fxaaEnabled: true,
+            bloomWeight: 0.05
         }
     }
 }

+ 2 - 0
Viewer/src/index.ts

@@ -42,5 +42,7 @@ function disposeAll() {
 
 const Version = BABYLON.Engine.Version;
 
+console.log("Babylon.js viewer (v" + Version + ")");
+
 // public API for initialization
 export { BABYLON, Version, InitTags, DefaultViewer, AbstractViewer, viewerGlobals, telemetryManager, disableInit, viewerManager, mapperManager, disposeAll, ModelLoader, ViewerModel, AnimationPlayMode, AnimationState, ModelState, ILoaderPlugin };

+ 6 - 6
Viewer/src/labs/viewerLabs.ts

@@ -9,7 +9,7 @@ export class ViewerLabs {
 
     constructor(private _sceneManager: SceneManager) { }
 
-    public environmentAssetsRootURL: string;
+    public assetsRootURL: string;
     public environment: PBREnvironment = {
         //irradiance
         irradiancePolynomialCoefficients: {
@@ -57,7 +57,7 @@ export class ViewerLabs {
             this.environment = EnvironmentDeserializer.Parse(data);
             if (onSuccess) onSuccess(this.environment);
         } else if (typeof data === 'string') {
-            let url = this.getEnvironmentAssetUrl(data);
+            let url = this.getAssetUrl(data);
             this._sceneManager.scene._loadFile(
                 url,
                 (arrayBuffer: ArrayBuffer) => {
@@ -124,15 +124,15 @@ export class ViewerLabs {
      * @param url Asset url
      * @returns The Asset url using the `environmentAssetsRootURL` if the url is not an absolute path.
      */
-    public getEnvironmentAssetUrl(url: string): string {
+    public getAssetUrl(url: string): string {
         let returnUrl = url;
         if (url && url.toLowerCase().indexOf("//") === -1) {
-            if (!this.environmentAssetsRootURL) {
-                Tools.Warn("Please, specify the root url of your assets before loading the configuration (labs.environmentAssetsRootURL) or disable the background through the viewer options.");
+            if (!this.assetsRootURL) {
+                // Tools.Warn("Please, specify the root url of your assets before loading the configuration (labs.environmentAssetsRootURL) or disable the background through the viewer options.");
                 return url;
             }
 
-            returnUrl = this.environmentAssetsRootURL + returnUrl;
+            returnUrl = this.assetsRootURL + returnUrl;
         }
 
         return returnUrl;

+ 28 - 4
Viewer/src/loader/modelLoader.ts

@@ -63,8 +63,18 @@ export class ModelLoader {
             return model;
         }
 
-        let filename = Tools.GetFilename(modelConfiguration.url) || modelConfiguration.url;
-        let base = modelConfiguration.root || Tools.GetFolderPath(modelConfiguration.url);
+        let base: string;
+        let filename: any;
+        if (modelConfiguration.file) {
+            base = "file:";
+            filename = modelConfiguration.file;
+        }
+        else {
+            filename = Tools.GetFilename(modelConfiguration.url) || modelConfiguration.url;
+            base = modelConfiguration.root || Tools.GetFolderPath(modelConfiguration.url);
+        }
+
+
         let plugin = modelConfiguration.loader;
 
         model.loader = SceneLoader.ImportMesh(undefined, base, filename, this._viewer.sceneManager.scene, (meshes, particleSystems, skeletons, animationGroups) => {
@@ -80,7 +90,9 @@ export class ModelLoader {
             }
 
             this._checkAndRun("onLoaded", model);
-            model.onLoadedObservable.notifyObserversWithPromise(model);
+            this._viewer.sceneManager.scene.executeWhenReady(() => {
+                model.onLoadedObservable.notifyObservers(model);
+            });
         }, (progressEvent) => {
             this._checkAndRun("onProgress", progressEvent);
             model.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
@@ -95,6 +107,12 @@ export class ModelLoader {
             let gltfLoader = (<GLTFFileLoader>model.loader);
             gltfLoader.animationStartMode = GLTFLoaderAnimationStartMode.NONE;
             gltfLoader.compileMaterials = true;
+
+            if (!modelConfiguration.file) {
+                gltfLoader.rewriteRootURL = (rootURL, responseURL) => {
+                    return modelConfiguration.root || Tools.GetFolderPath(responseURL || modelConfiguration.url || '');
+                };
+            }
             // if ground is set to "mirror":
             if (this._viewer.configuration.ground && typeof this._viewer.configuration.ground === 'object' && this._viewer.configuration.ground.mirror) {
                 gltfLoader.useClipPlane = true;
@@ -109,7 +127,13 @@ export class ModelLoader {
                 if (data && data.json && data.json['asset']) {
                     model.loadInfo = data.json['asset'];
                 }
-            })
+            });
+
+            gltfLoader.onCompleteObservable.add(() => {
+                model.loaderDone = true;
+            });
+        } else {
+            model.loaderDone = true;
         }
 
         this._checkAndRun("onInit", model.loader, model);

+ 0 - 2
Viewer/src/loader/plugins/extendedMaterialLoaderPlugin.ts

@@ -5,8 +5,6 @@ import { Color3, Texture, BaseTexture, Tools, ISceneLoaderPlugin, ISceneLoaderPl
 
 export class ExtendedMaterialLoaderPlugin implements ILoaderPlugin {
 
-    private _model: ViewerModel;
-
     public onMaterialLoaded(baseMaterial: Material) {
         var material = baseMaterial as PBRMaterial;
         material.alphaMode = Engine.ALPHA_PREMULTIPLIED_PORTERDUFF;

+ 1 - 1
Viewer/src/loader/plugins/minecraftLoaderPlugin.ts

@@ -16,7 +16,7 @@ export class MinecraftLoaderPlugin implements ILoaderPlugin {
         this._minecraftEnabled = false;
     }
 
-    public inParsed(data: IGLTFLoaderData) {
+    public onParsed(data: IGLTFLoaderData) {
         if (data && data.json && data.json['meshes'] && data.json['meshes'].length) {
             var meshes = data.json['meshes'] as GLTF2.IMesh[];
             for (var i = 0; i < meshes.length; i++) {

+ 48 - 3
Viewer/src/model/modelAnimation.ts

@@ -1,9 +1,9 @@
-import { AnimationGroup, Animatable, Skeleton } from "babylonjs";
+import { AnimationGroup, Animatable, Skeleton, Vector3 } from "babylonjs";
 
 /**
  * Animation play mode enum - is the animation looping or playing once
  */
-export enum AnimationPlayMode {
+export const enum AnimationPlayMode {
     ONCE,
     LOOP
 }
@@ -11,7 +11,7 @@ export enum AnimationPlayMode {
 /**
  * An enum representing the current state of an animation object
  */
-export enum AnimationState {
+export const enum AnimationState {
     INIT,
     PLAYING,
     PAUSED,
@@ -20,6 +20,51 @@ export enum AnimationState {
 }
 
 /**
+ * The different type of easing functions available 
+ */
+export const enum EasingFunction {
+    Linear = 0,
+    CircleEase = 1,
+    BackEase = 2,
+    BounceEase = 3,
+    CubicEase = 4,
+    ElasticEase = 5,
+    ExponentialEase = 6,
+    PowerEase = 7,
+    QuadraticEase = 8,
+    QuarticEase = 9,
+    QuinticEase = 10,
+    SineEase = 11
+}
+
+/**
+ * Defines a simple animation to be applied to a model (scale).
+ */
+export interface ModelAnimationConfiguration {
+    /**
+     * Time of animation, in seconds
+     */
+    time: number;
+
+    /**
+     * Scale to apply
+     */
+    scaling?: Vector3;
+
+    /**
+     * Easing function to apply
+     * See SPECTRE.EasingFunction
+     */
+    easingFunction?: number;
+
+    /**
+     * An Easing mode to apply to the easing function
+     * See BABYLON.EasingFunction
+     */
+    easingMode?: number;
+}
+
+/**
  * This interface can be implemented to define new types of ModelAnimation objects.
  */
 export interface IModelAnimation {

+ 294 - 27
Viewer/src/model/viewerModel.ts

@@ -1,7 +1,7 @@
-import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, Quaternion, Material, Vector3, AnimationPropertiesOverride } from "babylonjs";
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation, Quaternion, Material, Vector3, AnimationPropertiesOverride, QuinticEase, SineEase, CircleEase, BackEase, BounceEase, CubicEase, ElasticEase, ExponentialEase, PowerEase, QuadraticEase, QuarticEase, PBRMaterial, MultiMaterial } from "babylonjs";
 import { GLTFFileLoader, GLTF2 } from "babylonjs-loaders";
-import { IModelConfiguration } from "../configuration/configuration";
-import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
+import { IModelConfiguration, IModelAnimationConfiguration } from "../configuration/configuration";
+import { IModelAnimation, GroupModelAnimation, AnimationPlayMode, ModelAnimationConfiguration, EasingFunction } from "./modelAnimation";
 
 import * as deepmerge from '../../assets/deepmerge.min.js';
 import { AbstractViewer } from "..";
@@ -12,6 +12,9 @@ export enum ModelState {
     INIT,
     LOADING,
     LOADED,
+    ENTRY,
+    ENTRYDONE,
+    COMPLETE,
     CANCELED,
     ERROR
 }
@@ -20,7 +23,6 @@ export enum ModelState {
  * The viewer model is a container for all assets representing a sngle loaded model.
  */
 export class ViewerModel implements IDisposable {
-
     /**
      * The loader used to load this model.
      */
@@ -37,6 +39,8 @@ export class ViewerModel implements IDisposable {
      * This mesh does not(!) exist in the meshes array.
      */
     public rootMesh: AbstractMesh;
+
+    private _pivotMesh: AbstractMesh;
     /**
      * ParticleSystems connected to this model
      */
@@ -65,6 +69,10 @@ export class ViewerModel implements IDisposable {
     public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
 
     /**
+     * Will be executed after the model finished loading and complete, including entry animation and lod
+     */
+    public onCompleteObservable: Observable<ViewerModel>;
+    /**
      * Observers registered here will be executed every time the model is being configured.
      * This can be used to extend the model's configuration without extending the class itself
      */
@@ -83,15 +91,30 @@ export class ViewerModel implements IDisposable {
     private _loadedUrl: string;
     private _modelConfiguration: IModelConfiguration;
 
+    private _loaderDone: boolean = false;
+
+    private _entryAnimation: ModelAnimationConfiguration;
+    private _exitAnimation: ModelAnimationConfiguration;
+    private _scaleTransition: Animation;
+    private _animatables: Array<Animatable> = [];
+    private _frameRate: number = 60;
+
     constructor(protected _viewer: AbstractViewer, modelConfiguration: IModelConfiguration) {
         this.onLoadedObservable = new Observable();
         this.onLoadErrorObservable = new Observable();
         this.onLoadProgressObservable = new Observable();
+        this.onCompleteObservable = new Observable();
         this.onAfterConfigure = new Observable();
 
         this.state = ModelState.INIT;
 
         this.rootMesh = new AbstractMesh("modelRootMesh", this._viewer.sceneManager.scene);
+        this._pivotMesh = new AbstractMesh("pivotMesh", this._viewer.sceneManager.scene);
+        this._pivotMesh.parent = this.rootMesh;
+        // rotate 180, gltf fun
+        this._pivotMesh.rotation.y += Math.PI;
+
+        this._scaleTransition = new Animation("scaleAnimation", "scaling", this._frameRate, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
 
         this._animations = [];
         //create a copy of the configuration to make sure it doesn't change even after it is changed in the viewer
@@ -100,9 +123,14 @@ export class ViewerModel implements IDisposable {
         this._viewer.sceneManager.models.push(this);
         this._viewer.onModelAddedObservable.notifyObservers(this);
         this.onLoadedObservable.add(() => {
+            this.updateConfiguration(this._modelConfiguration);
             this._viewer.onModelLoadedObservable.notifyObservers(this);
             this._initAnimations();
         });
+
+        this.onCompleteObservable.add(() => {
+            this.state = ModelState.COMPLETE;
+        });
     }
 
     /**
@@ -119,6 +147,17 @@ export class ViewerModel implements IDisposable {
         this.rootMesh.setEnabled(enable);
     }
 
+    public set loaderDone(done: boolean) {
+        this._loaderDone = done;
+        this._checkCompleteState();
+    }
+
+    private _checkCompleteState() {
+        if (this._loaderDone && (this.state === ModelState.ENTRYDONE)) {
+            this._modelComplete();
+        }
+    }
+
     /**
      * Get the viewer showing this model
      */
@@ -135,7 +174,7 @@ export class ViewerModel implements IDisposable {
      */
     public addMesh(mesh: AbstractMesh, triggerLoaded?: boolean) {
         if (!mesh.parent) {
-            mesh.parent = this.rootMesh;
+            mesh.parent = this._pivotMesh;
         }
         mesh.receiveShadows = !!this.configuration.receiveShadows;
         this._meshes.push(mesh);
@@ -151,8 +190,6 @@ export class ViewerModel implements IDisposable {
         return this._meshes;
     }
 
-    public get
-
     /**
      * Get the model's configuration
      */
@@ -195,7 +232,9 @@ export class ViewerModel implements IDisposable {
             });
         }
 
-        if (!this._modelConfiguration) return;
+        let completeCallback = () => {
+
+        }
 
         if (this._modelConfiguration.animation) {
             if (this._modelConfiguration.animation.playOnce) {
@@ -206,9 +245,57 @@ export class ViewerModel implements IDisposable {
             if (this._modelConfiguration.animation.autoStart && this._animations.length) {
                 let animationName = this._modelConfiguration.animation.autoStart === true ?
                     this._animations[0].name : this._modelConfiguration.animation.autoStart;
-                this.playAnimation(animationName);
+
+                completeCallback = () => {
+                    this.playAnimation(animationName);
+                }
             }
         }
+
+        this._enterScene(completeCallback);
+    }
+
+    /**
+     * Animates the model from the current position to the default position
+     * @param completeCallback A function to call when the animation has completed
+     */
+    private _enterScene(completeCallback?: () => void): void {
+        let callback = () => {
+            this.state = ModelState.ENTRYDONE;
+            this._viewer.sceneManager.animationBlendingEnabled = true;
+            this._checkCompleteState();
+            if (completeCallback) completeCallback();
+        }
+        if (!this._entryAnimation) {
+            callback();
+            return;
+        }
+        // disable blending for the sake of the entry animation;
+        this._viewer.sceneManager.animationBlendingEnabled = false;
+        this._applyAnimation(this._entryAnimation, true, callback);
+    }
+
+    /**
+     * Animates the model from the current position to the exit-screen position
+     * @param completeCallback A function to call when the animation has completed
+     */
+    private _exitScene(completeCallback: () => void): void {
+        if (!this._exitAnimation) {
+            completeCallback();
+            return;
+        }
+
+        this._applyAnimation(this._exitAnimation, false, completeCallback);
+    }
+
+    private _modelComplete() {
+        //reapply material defines to be sure:
+        let meshes = this._pivotMesh.getChildMeshes(false);
+        meshes.filter(m => m.material).forEach((mesh) => {
+            this._applyModelMaterialConfiguration(mesh.material!);
+        });
+        this.state = ModelState.COMPLETE;
+        this.onCompleteObservable.notifyObservers(this);
     }
 
     /**
@@ -311,7 +398,7 @@ export class ViewerModel implements IDisposable {
             if (parentIndex !== undefined) {
                 meshesToNormalize.push(this._meshes[parentIndex]);
             } else {
-                meshesToNormalize = meshesWithNoParent;
+                meshesToNormalize = this._pivotMesh.getChildMeshes(true).length === 1 ? [this._pivotMesh] : meshesWithNoParent;
             }
 
             if (unitSize) {
@@ -328,22 +415,14 @@ export class ViewerModel implements IDisposable {
                     const center = boundingInfo.min.add(halfSizeVec);
                     mesh.position = center.scale(-1);
 
+                    mesh.position.y += halfSizeVec.y;
+
                     // Recompute Info.
                     mesh.computeWorldMatrix(true);
                 });
             }
         } else {
-            //center automatically
-            meshesWithNoParent.forEach(mesh => {
-                const boundingInfo = mesh.getHierarchyBoundingVectors(true);
-                const sizeVec = boundingInfo.max.subtract(boundingInfo.min);
-                const halfSizeVec = sizeVec.scale(0.5);
-                const center = boundingInfo.min.add(halfSizeVec);
-                mesh.position = center.scale(-1);
-
-                // Recompute Info.
-                mesh.computeWorldMatrix(true);
-            });
+            // if centered, should be done here
         }
 
         // position?
@@ -371,7 +450,7 @@ export class ViewerModel implements IDisposable {
                 if (this._modelConfiguration.rotationOffsetAngle) {
                     m.rotate(rotationAxis, this._modelConfiguration.rotationOffsetAngle);
                 }
-            })
+            });
 
         }
 
@@ -385,14 +464,40 @@ export class ViewerModel implements IDisposable {
             });
         }
 
-        let meshes = this.rootMesh.getChildMeshes(false);
+        let meshes = this._pivotMesh.getChildMeshes(false);
         meshes.filter(m => m.material).forEach((mesh) => {
             this._applyModelMaterialConfiguration(mesh.material!);
         });
 
+        if (this._modelConfiguration.entryAnimation) {
+            this._entryAnimation = this._modelAnimationConfigurationToObject(this._modelConfiguration.entryAnimation);
+        }
+
+        if (this._modelConfiguration.exitAnimation) {
+            this._exitAnimation = this._modelAnimationConfigurationToObject(this._modelConfiguration.exitAnimation);
+        }
+
+
         this.onAfterConfigure.notifyObservers(this);
     }
 
+    private _modelAnimationConfigurationToObject(animConfig: IModelAnimationConfiguration): ModelAnimationConfiguration {
+        let anim: ModelAnimationConfiguration = {
+            time: 0.5
+        };
+        if (animConfig.scaling) {
+            anim.scaling = Vector3.Zero();
+        }
+        if (animConfig.easingFunction !== undefined) {
+            anim.easingFunction = animConfig.easingFunction;
+        }
+        if (animConfig.easingMode !== undefined) {
+            anim.easingMode = animConfig.easingMode;
+        }
+        extendClassWithConfig(anim, animConfig);
+        return anim;
+    }
+
     /**
      * Apply a material configuration to a material
      * @param material Material to apply configuration to
@@ -402,7 +507,7 @@ export class ViewerModel implements IDisposable {
 
         extendClassWithConfig(material, this._modelConfiguration.material);
 
-        if (material instanceof BABYLON.PBRMaterial) {
+        if (material instanceof PBRMaterial) {
             if (this._modelConfiguration.material.directIntensity !== undefined) {
                 material.directIntensity = this._modelConfiguration.material.directIntensity;
             }
@@ -418,11 +523,11 @@ export class ViewerModel implements IDisposable {
             if (this._modelConfiguration.material.directEnabled !== undefined) {
                 material.disableLighting = !this._modelConfiguration.material.directEnabled;
             }
-            if (this._viewer.sceneManager.mainColor) {
-                material.reflectionColor = this._viewer.sceneManager.mainColor;
+            if (this._viewer.sceneManager.reflectionColor) {
+                material.reflectionColor = this._viewer.sceneManager.reflectionColor;
             }
         }
-        else if (material instanceof BABYLON.MultiMaterial) {
+        else if (material instanceof MultiMaterial) {
             for (let i = 0; i < material.subMaterials.length; i++) {
                 const subMaterial = material.subMaterials[i];
                 if (subMaterial) {
@@ -433,9 +538,171 @@ export class ViewerModel implements IDisposable {
     }
 
     /**
+     * Start entry/exit animation given an animation configuration
+     * @param animationConfiguration Entry/Exit animation configuration
+     * @param isEntry Pass true if the animation is an entry animation
+     * @param completeCallback Callback to execute when the animation completes
+     */
+    private _applyAnimation(animationConfiguration: ModelAnimationConfiguration, isEntry: boolean, completeCallback?: () => void) {
+        let animations: Animation[] = [];
+
+        //scale
+        if (animationConfiguration.scaling) {
+
+            let scaleStart: Vector3 = isEntry ? animationConfiguration.scaling : new Vector3(1, 1, 1);
+            let scaleEnd: Vector3 = isEntry ? new Vector3(1, 1, 1) : animationConfiguration.scaling;
+
+            if (!scaleStart.equals(scaleEnd)) {
+                this.rootMesh.scaling = scaleStart;
+                this._setLinearKeys(
+                    this._scaleTransition,
+                    this.rootMesh.scaling,
+                    scaleEnd,
+                    animationConfiguration.time
+                );
+                animations.push(this._scaleTransition);
+            }
+        }
+
+        //Start the animation(s)
+        this.transitionTo(
+            animations,
+            animationConfiguration.time,
+            this._createEasingFunction(animationConfiguration.easingFunction),
+            animationConfiguration.easingMode,
+            () => { if (completeCallback) completeCallback(); }
+        );
+    }
+
+    /**
+    * Begin @animations with the specified @easingFunction
+    * @param animations The BABYLON Animations to begin
+    * @param duration of transition, in seconds
+    * @param easingFunction An easing function to apply
+    * @param easingMode A easing mode to apply to the easingFunction
+    * @param onAnimationEnd Call back trigger at the end of the animation.
+    */
+    public transitionTo(
+        animations: Animation[],
+        duration: number,
+        easingFunction: any,
+        easingMode: number = BABYLON.EasingFunction.EASINGMODE_EASEINOUT,
+        onAnimationEnd: () => void): void {
+
+        if (easingFunction) {
+            for (let animation of animations) {
+                easingFunction.setEasingMode(easingMode);
+                animation.setEasingFunction(easingFunction);
+            }
+        }
+
+        //Stop any current animations before starting the new one - merging not yet supported.
+        this.stopAllAnimations();
+
+        this.rootMesh.animations = animations;
+
+        if (this._viewer.sceneManager.scene.beginAnimation) {
+            let animatable: Animatable = this._viewer.sceneManager.scene.beginAnimation(this.rootMesh, 0, this._frameRate * duration, false, 1, () => {
+                if (onAnimationEnd) {
+                    onAnimationEnd();
+                }
+            });
+            this._animatables.push(animatable);
+        }
+    }
+
+    /**
+     * Sets key values on an Animation from first to last frame.
+     * @param animation The Babylon animation object to set keys on
+     * @param startValue The value of the first key
+     * @param endValue The value of the last key
+     * @param duration The duration of the animation, used to determine the end frame
+     */
+    private _setLinearKeys(animation: Animation, startValue: any, endValue: any, duration: number) {
+        animation.setKeys([
+            {
+                frame: 0,
+                value: startValue
+            },
+            {
+                frame: this._frameRate * duration,
+                value: endValue
+            }
+        ]);
+    }
+
+    /**
+     * Creates and returns a Babylon easing funtion object based on a string representing the Easing function
+     * @param easingFunctionID The enum of the easing funtion to create
+     * @return The newly created Babylon easing function object
+     */
+    private _createEasingFunction(easingFunctionID?: number): any {
+        let easingFunction;
+
+        switch (easingFunctionID) {
+            case EasingFunction.CircleEase:
+                easingFunction = new CircleEase();
+                break;
+            case EasingFunction.BackEase:
+                easingFunction = new BackEase(0.3);
+                break;
+            case EasingFunction.BounceEase:
+                easingFunction = new BounceEase();
+                break;
+            case EasingFunction.CubicEase:
+                easingFunction = new CubicEase();
+                break;
+            case EasingFunction.ElasticEase:
+                easingFunction = new ElasticEase();
+                break;
+            case EasingFunction.ExponentialEase:
+                easingFunction = new ExponentialEase();
+                break;
+            case EasingFunction.PowerEase:
+                easingFunction = new PowerEase();
+                break;
+            case EasingFunction.QuadraticEase:
+                easingFunction = new QuadraticEase();
+                break;
+            case EasingFunction.QuarticEase:
+                easingFunction = new QuarticEase();
+                break;
+            case EasingFunction.QuinticEase:
+                easingFunction = new QuinticEase();
+                break;
+            case EasingFunction.SineEase:
+                easingFunction = new SineEase();
+                break;
+            default:
+                Tools.Log("No ease function found");
+                break;
+        }
+
+        return easingFunction;
+    }
+
+    /**
+     * Stops and removes all animations that have been applied to the model
+     */
+    public stopAllAnimations(): void {
+        if (this.rootMesh) {
+            this.rootMesh.animations = [];
+        }
+        if (this.currentAnimation) {
+            this.currentAnimation.stop();
+        }
+        while (this._animatables.length) {
+            this._animatables[0].onAnimationEnd = null;
+            this._animatables[0].stop();
+            this._animatables.shift();
+        }
+    }
+
+    /**
      * Will remove this model from the viewer (but NOT dispose it).
      */
     public remove() {
+        this.stopAllAnimations();
         this._viewer.sceneManager.models.splice(this._viewer.sceneManager.models.indexOf(this), 1);
         // hide it
         this.rootMesh.isVisible = false;

+ 94 - 0
Viewer/src/optimizer/custom/extended.ts

@@ -0,0 +1,94 @@
+import { AbstractViewer } from '../../viewer/viewer';
+import { Scalar, DefaultRenderingPipeline } from 'babylonjs';
+
+export function extendedUpgrade(viewer: AbstractViewer): boolean {
+    let defaultPipeline = <DefaultRenderingPipeline>viewer.sceneManager.defaultRenderingPipeline;
+    // if (!this.Scene.BackgroundHelper) {
+    // 	this.Scene.EngineScene.autoClear = false;
+    // this.Scene.BackgroundHelper = true;
+    // Would require a dedicated clear color;
+    // return false;
+    // }
+    if (viewer.engine.getHardwareScalingLevel() > 1) {
+        let scaling = Scalar.Clamp(viewer.engine.getHardwareScalingLevel() - 0.25, 0, 1);
+        viewer.engine.setHardwareScalingLevel(scaling);
+        return false;
+    }
+    if (!viewer.sceneManager.scene.postProcessesEnabled) {
+        viewer.sceneManager.scene.postProcessesEnabled = true;
+        return false;
+    }
+    if (!viewer.sceneManager.groundEnabled) {
+        viewer.sceneManager.groundEnabled = true;
+        return false;
+    }
+    if (defaultPipeline && !viewer.sceneManager.fxaaEnabled) {
+        viewer.sceneManager.fxaaEnabled = true
+        return false;
+    }
+    var hardwareScalingLevel = Math.max(1 / 2, 1 / (window.devicePixelRatio || 2));
+    if (viewer.engine.getHardwareScalingLevel() > hardwareScalingLevel) {
+        let scaling = Scalar.Clamp(viewer.engine.getHardwareScalingLevel() - 0.25, 0, hardwareScalingLevel);
+        viewer.engine.setHardwareScalingLevel(scaling);
+        return false;
+    }
+    if (!viewer.sceneManager.processShadows) {
+        viewer.sceneManager.processShadows = true;
+        return false;
+    }
+    if (defaultPipeline && !viewer.sceneManager.bloomEnabled) {
+        viewer.sceneManager.bloomEnabled = true
+        return false;
+    }
+    if (!viewer.sceneManager.groundMirrorEnabled) {
+        viewer.sceneManager.groundMirrorEnabled = true;
+        return false;
+    }
+    return true;
+}
+
+export function extendedDegrade(viewer: AbstractViewer): boolean {
+    let defaultPipeline = <DefaultRenderingPipeline>viewer.sceneManager.defaultRenderingPipeline;
+
+    if (viewer.sceneManager.groundMirrorEnabled) {
+        viewer.sceneManager.groundMirrorEnabled = false;
+        return false;
+    }
+    if (defaultPipeline && viewer.sceneManager.bloomEnabled) {
+        viewer.sceneManager.bloomEnabled = false;
+        return false;
+    }
+    if (viewer.sceneManager.processShadows) {
+        viewer.sceneManager.processShadows = false;
+        return false;
+    }
+    if (viewer.engine.getHardwareScalingLevel() < 1) {
+        let scaling = Scalar.Clamp(viewer.engine.getHardwareScalingLevel() + 0.25, 0, 1);
+        viewer.engine.setHardwareScalingLevel(scaling);
+        return false;
+    }
+    if (defaultPipeline && viewer.sceneManager.fxaaEnabled) {
+        viewer.sceneManager.fxaaEnabled = false;
+        return false;
+    }
+    if (viewer.sceneManager.groundEnabled) {
+        viewer.sceneManager.groundEnabled = false;
+        return false;
+    }
+    if (viewer.sceneManager.scene.postProcessesEnabled) {
+        viewer.sceneManager.scene.postProcessesEnabled = false;
+        return false;
+    }
+    if (viewer.engine.getHardwareScalingLevel() < 1.25) {
+        let scaling = Scalar.Clamp(viewer.engine.getHardwareScalingLevel() + 0.25, 0, 1.25);
+        viewer.engine.setHardwareScalingLevel(scaling);
+        return false;
+    }
+    // if (this.Scene.BackgroundHelper) {
+    // 	this.Scene.EngineScene.autoClear = true;
+    // this.Scene.BackgroundHelper = false;
+    // Would require a dedicated clear color;
+    // return false;
+    // }
+    return true;
+}

+ 20 - 0
Viewer/src/optimizer/custom/index.ts

@@ -0,0 +1,20 @@
+import { AbstractViewer } from "../../viewer/viewer";
+import { extendedUpgrade, extendedDegrade } from "./extended";
+
+const cache: { [key: string]: (viewer: AbstractViewer) => boolean } = {};
+
+export function getCustomOptimizerByName(name: string, upgrade?: boolean) {
+    if (!cache[name]) {
+        switch (name) {
+            case 'extended':
+                if (upgrade) {
+                    return extendedUpgrade;
+                }
+                else {
+                    return extendedDegrade;
+                }
+        }
+    }
+
+    return cache[name];
+}

+ 4 - 6
Viewer/src/viewer/defaultViewer.ts

@@ -22,10 +22,6 @@ 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;
-        });
 
         this.sceneManager.onLightsConfiguredObservable.add((data) => {
             this._configureLights(data.newConfiguration, data.model!);
@@ -182,12 +178,14 @@ export class DefaultViewer extends AbstractViewer {
     private _onModelLoaded = (model: ViewerModel) => {
         this._configureTemplate(model);
         // with a short timeout, making sure everything is there already.
-        let hideLoadingDelay = 500;
+        let hideLoadingDelay = 20;
         if (this._configuration.lab && this._configuration.lab.hideLoadingDelay !== undefined) {
             hideLoadingDelay = this._configuration.lab.hideLoadingDelay;
         }
         setTimeout(() => {
-            this.hideLoadingScreen();
+            this.sceneManager.scene.executeWhenReady(() => {
+                this.hideLoadingScreen();
+            });
         }, hideLoadingDelay);
 
         return;

文件差异内容过多而无法显示
+ 396 - 123
Viewer/src/viewer/sceneManager.ts


文件差异内容过多而无法显示
+ 11 - 7
Viewer/src/viewer/viewer.ts


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

@@ -76,7 +76,7 @@ describe('Viewer', function () {
         let viewer: DefaultViewer = <DefaultViewer>Helper.getNewViewerInstance();
         let renderCount = 0;
         let sceneRenderCount = 0;
-        viewer.onSceneInitObservable.add(() => {
+        viewer.onSceneInitObservable.add((scene) => {
             viewer.sceneManager.scene.registerBeforeRender(() => {
                 sceneRenderCount++;
             });
@@ -174,13 +174,13 @@ describe('Viewer', function () {
     it('should render in background if set to true', (done) => {
         let viewer = Helper.getNewViewerInstance();
         viewer.onInitDoneObservable.add(() => {
-            assert.isFalse(viewer.engine.renderEvenInBackground, "Engine is rendering in background");
+            assert.isTrue(viewer.engine.renderEvenInBackground, "Engine is rendering in background");
             viewer.updateConfiguration({
                 scene: {
-                    renderInBackground: true
+                    renderInBackground: false
                 }
             });
-            assert.isTrue(viewer.engine.renderEvenInBackground, "Engine is not rendering in background");
+            assert.isFalse(viewer.engine.renderEvenInBackground, "Engine is not rendering in background");
             viewer.dispose();
             done();
         });