فهرست منبع

Merge branch 'master' into addAllToMoveAllFromSceneForum

Trevor Baron 7 سال پیش
والد
کامیت
82e97efc66

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 7683 - 7599
Playground/babylon.d.txt


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 30
Viewer/dist/viewer.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 30
Viewer/dist/viewer.min.js


+ 153 - 133
Viewer/src/configuration/configuration.ts

@@ -15,143 +15,20 @@ export interface ViewerConfiguration {
     };
     };
 
 
     // names of functions in the window context.
     // names of functions in the window context.
-    observers?: {
-        onEngineInit?: string;
-        onSceneInit?: string;
-        onModelLoaded?: string;
-    }
+    observers?: IObserversConfiguration;
 
 
     canvasElement?: string; // if there is a need to override the standard implementation - ID of HTMLCanvasElement
     canvasElement?: string; // if there is a need to override the standard implementation - ID of HTMLCanvasElement
 
 
-    model?: {
-        url?: string;
-        loader?: string; // obj, gltf?
-        position?: { x: number, y: number, z: number };
-        rotation?: { x: number, y: number, z: number, w: number };
-        scaling?: { x: number, y: number, z: number };
-        parentObjectIndex?: number; // the index of the parent object of the model in the loaded meshes array.
-
-        title: string;
-        subtitle?: string;
-        thumbnail?: string; // URL or data-url
-
-        [propName: string]: any; // further configuration, like title and creator
-    } | string;
-
-    scene?: {
-        debug?: boolean;
-        autoRotate?: boolean;
-        rotationSpeed?: number;
-        defaultCamera?: boolean;
-        defaultLight?: boolean;
-        clearColor?: { r: number, g: number, b: number, a: number };
-        imageProcessingConfiguration?: IImageProcessingConfiguration;
-        environmentTexture?: string;
-    },
-    optimizer?: {
-        targetFrameRate?: number;
-        trackerDuration?: number;
-        autoGeneratePriorities?: boolean;
-        improvementMode?: boolean;
-        degradation?: string; // low, moderate, high
-        types?: {
-            texture?: SceneOptimizerParameters;
-            hardwareScaling?: SceneOptimizerParameters;
-            shadow?: SceneOptimizerParameters;
-            postProcess?: SceneOptimizerParameters;
-            lensFlare?: SceneOptimizerParameters;
-            particles?: SceneOptimizerParameters;
-            renderTarget?: SceneOptimizerParameters;
-            mergeMeshes?: SceneOptimizerParameters;
-        }
-    },
-    // at the moment, support only a single camera.
-    camera?: {
-        position?: { x: number, y: number, z: number };
-        rotation?: { x: number, y: number, z: number, w: number };
-        fov?: number;
-        fovMode?: number;
-        minZ?: number;
-        maxZ?: number;
-        inertia?: number;
-        behaviors?: {
-            [name: string]: number | {
-                type: number;
-                [propName: string]: any;
-            };
-        };
-
-        [propName: string]: any;
-    },
-    skybox?: {
-        cubeTexture?: {
-            noMipMap?: boolean;
-            gammaSpace?: boolean;
-            url?: string | Array<string>;
-        };
-        color?: { r: number, g: number, b: number };
-        pbr?: boolean; // deprecated
-        scale?: number;
-        blur?: number; // deprecated
-        material?: {
-            imageProcessingConfiguration?: IImageProcessingConfiguration;
-        };
-        infiniteDIstance?: boolean;
-
-    };
-
-    ground?: boolean | {
-        size?: number;
-        receiveShadows?: boolean;
-        shadowLevel?: number;
-        shadowOnly?: boolean; // deprecated
-        mirror?: boolean | {
-            sizeRatio?: number;
-            blurKernel?: number;
-            amount?: number;
-            fresnelWeight?: number;
-            fallOffDistance?: number;
-            textureType?: number;
-        };
-        texture?: string;
-        color?: { r: number, g: number, b: number };
-        opacity?: number;
-        material?: { // deprecated!
-            [propName: string]: any;
-        };
+    model?: IModelConfiguration | string;
 
 
-    };
-    lights?: {
-        [name: string]: {
-            type: number;
-            name?: string;
-            disabled?: boolean;
-            position?: { x: number, y: number, z: number };
-            target?: { x: number, y: number, z: number };
-            direction?: { x: number, y: number, z: number };
-            diffuse?: { r: number, g: number, b: number };
-            specular?: { r: number, g: number, b: number };
-            intensity?: number;
-            radius?: number;
-            shadownEnabled?: boolean; // only on specific lights!
-            shadowConfig?: {
-                useBlurExponentialShadowMap?: boolean;
-                useKernelBlur?: boolean;
-                blurKernel?: number;
-                blurScale?: number;
-                [propName: string]: any;
-            }
-            [propName: string]: any;
+    scene?: ISceneConfiguration;
+    optimizer?: ISceneOptimizerConfiguration | boolean;
+    // at the moment, support only a single camera.
+    camera?: ICameraConfiguration,
+    skybox?: boolean | ISkyboxConfiguration;
 
 
-            // no behaviors for light at the moment, but allowing configuration for future reference.
-            behaviors?: {
-                [name: string]: number | {
-                    type: number;
-                    [propName: string]: any;
-                };
-            };
-        }
-    },
+    ground?: boolean | IGroundConfiguration;
+    lights?: { [name: string]: boolean | ILightConfiguration },
     // engine configuration. optional!
     // engine configuration. optional!
     engine?: {
     engine?: {
         antialiasing?: boolean;
         antialiasing?: boolean;
@@ -189,7 +66,150 @@ export interface ViewerConfiguration {
     }
     }
 }
 }
 
 
-export interface SceneOptimizerParameters {
+export interface IModelConfiguration {
+    url: string;
+    loader?: string; // obj, gltf?
+    position?: { x: number, y: number, z: number };
+    rotation?: { x: number, y: number, z: number, w?: number };
+    scaling?: { x: number, y: number, z: number };
+    parentObjectIndex?: number; // the index of the parent object of the model in the loaded meshes array.
+
+    castShadow?: boolean;
+
+    title: string;
+    subtitle?: string;
+    thumbnail?: string; // URL or data-url
+
+    // [propName: string]: any; // further configuration, like title and creator
+}
+
+export interface ISkyboxConfiguration {
+    cubeTexture?: {
+        noMipMap?: boolean;
+        gammaSpace?: boolean;
+        url?: string | Array<string>;
+    };
+    color?: { r: number, g: number, b: number };
+    pbr?: boolean; // deprecated
+    scale?: number;
+    blur?: number; // deprecated
+    material?: {
+        imageProcessingConfiguration?: IImageProcessingConfiguration;
+    };
+    infiniteDIstance?: boolean;
+
+}
+
+export interface IGroundConfiguration {
+    size?: number;
+    receiveShadows?: boolean;
+    shadowLevel?: number;
+    shadowOnly?: boolean; // deprecated
+    mirror?: boolean | {
+        sizeRatio?: number;
+        blurKernel?: number;
+        amount?: number;
+        fresnelWeight?: number;
+        fallOffDistance?: number;
+        textureType?: number;
+    };
+    texture?: string;
+    color?: { r: number, g: number, b: number };
+    opacity?: number;
+    material?: { // deprecated!
+        [propName: string]: any;
+    };
+}
+
+export interface ISceneConfiguration {
+    debug?: boolean;
+    autoRotate?: boolean; // deprecated
+    rotationSpeed?: number; // deprecated
+    defaultCamera?: boolean; // deprecated
+    defaultLight?: boolean; // deprecated
+    clearColor?: { r: number, g: number, b: number, a: number };
+    imageProcessingConfiguration?: IImageProcessingConfiguration;
+    environmentTexture?: string;
+}
+
+export interface ISceneOptimizerConfiguration {
+    targetFrameRate?: number;
+    trackerDuration?: number;
+    autoGeneratePriorities?: boolean;
+    improvementMode?: boolean;
+    degradation?: string; // low, moderate, high
+    types?: {
+        texture?: ISceneOptimizerParameters;
+        hardwareScaling?: ISceneOptimizerParameters;
+        shadow?: ISceneOptimizerParameters;
+        postProcess?: ISceneOptimizerParameters;
+        lensFlare?: ISceneOptimizerParameters;
+        particles?: ISceneOptimizerParameters;
+        renderTarget?: ISceneOptimizerParameters;
+        mergeMeshes?: ISceneOptimizerParameters;
+    }
+}
+
+export interface IObserversConfiguration {
+    onEngineInit?: string;
+    onSceneInit?: string;
+    onModelLoaded?: string;
+}
+
+export interface ICameraConfiguration {
+    position?: { x: number, y: number, z: number };
+    rotation?: { x: number, y: number, z: number, w: number };
+    fov?: number;
+    fovMode?: number;
+    minZ?: number;
+    maxZ?: number;
+    inertia?: number;
+    behaviors?: {
+        [name: string]: number | {
+            type: number;
+            [propName: string]: any;
+        };
+    };
+
+    [propName: string]: any;
+}
+
+export interface ILightConfiguration {
+    type: number;
+    name?: string;
+    disabled?: boolean;
+    position?: { x: number, y: number, z: number };
+    target?: { x: number, y: number, z: number };
+    direction?: { x: number, y: number, z: number };
+    diffuse?: { r: number, g: number, b: number };
+    specular?: { r: number, g: number, b: number };
+    intensity?: number;
+    intensityMode?: number;
+    radius?: number;
+    shadownEnabled?: boolean; // only on specific lights!
+    shadowConfig?: {
+        useBlurExponentialShadowMap?: boolean;
+        useKernelBlur?: boolean;
+        blurKernel?: number;
+        blurScale?: number;
+        minZ?: number;
+        maxZ?: number;
+        frustumSize?: number;
+        angleScale?: number;
+        [propName: string]: any;
+    }
+    [propName: string]: any;
+
+    // no behaviors for light at the moment, but allowing configuration for future reference.
+    behaviors?: {
+        [name: string]: number | {
+            type: number;
+            [propName: string]: any;
+        };
+    };
+}
+
+export interface ISceneOptimizerParameters {
     priority?: number;
     priority?: number;
     maximumSize?: number;
     maximumSize?: number;
     step?: number;
     step?: number;

+ 32 - 174
Viewer/src/viewer/defaultViewer.ts

@@ -1,6 +1,6 @@
 
 
 
 
-import { ViewerConfiguration } from './../configuration/configuration';
+import { ViewerConfiguration, IModelConfiguration, ILightConfiguration } from './../configuration/configuration';
 import { Template, EventCallback } from './../templateManager';
 import { Template, EventCallback } from './../templateManager';
 import { AbstractViewer } from './viewer';
 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 { 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';
@@ -8,8 +8,6 @@ import { CameraBehavior } from '../interfaces';
 
 
 export class DefaultViewer extends AbstractViewer {
 export class DefaultViewer extends AbstractViewer {
 
 
-    public camera: ArcRotateCamera;
-
     constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) {
     constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) {
         super(containerElement, initialConfiguration);
         super(containerElement, initialConfiguration);
         this.onModelLoadedObservable.add(this.onModelLoaded);
         this.onModelLoadedObservable.add(this.onModelLoaded);
@@ -105,6 +103,34 @@ export class DefaultViewer extends AbstractViewer {
         this.containerElement.style.display = 'flex';
         this.containerElement.style.display = 'flex';
     }
     }
 
 
+    protected configureModel(modelConfiguration: Partial<IModelConfiguration>) {
+        super.configureModel(modelConfiguration);
+
+        let navbar = this.templateManager.getTemplate('navBar');
+        if (!navbar) return;
+
+        let metadataContainer = navbar.parent.querySelector('#model-metadata');
+        if (metadataContainer) {
+            if (modelConfiguration.title !== undefined) {
+                let element = metadataContainer.querySelector('span.model-title');
+                if (element) {
+                    element.innerHTML = modelConfiguration.title;
+                }
+            }
+
+            if (modelConfiguration.subtitle !== undefined) {
+                let element = metadataContainer.querySelector('span.model-subtitle');
+                if (element) {
+                    element.innerHTML = modelConfiguration.subtitle;
+                }
+            }
+
+            if (modelConfiguration.thumbnail !== undefined) {
+                (<HTMLDivElement>metadataContainer.querySelector('.thumbnail')).style.backgroundImage = `url('${modelConfiguration.thumbnail}')`;
+            }
+        }
+    }
+
     public loadModel(model: any = this.configuration.model): Promise<Scene> {
     public loadModel(model: any = this.configuration.model): Promise<Scene> {
         this.showLoadingScreen();
         this.showLoadingScreen();
         return super.loadModel(model, true).catch((error) => {
         return super.loadModel(model, true).catch((error) => {
@@ -116,10 +142,6 @@ export class DefaultViewer extends AbstractViewer {
     }
     }
 
 
     private onModelLoaded = (meshes: Array<AbstractMesh>) => {
     private onModelLoaded = (meshes: Array<AbstractMesh>) => {
-
-        // here we could set the navbar's model information:
-        this.setModelMetaData();
-
         // with a short timeout, making sure everything is there already.
         // with a short timeout, making sure everything is there already.
         let hideLoadingDelay = 500;
         let hideLoadingDelay = 500;
         if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
         if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
@@ -129,48 +151,11 @@ export class DefaultViewer extends AbstractViewer {
             this.hideLoadingScreen();
             this.hideLoadingScreen();
         }, hideLoadingDelay);
         }, hideLoadingDelay);
 
 
-
-        // recreate the camera
-        this.scene.createDefaultCameraOrLight(true, true, true);
-        this.camera = <ArcRotateCamera>this.scene.activeCamera;
-
         meshes[0].rotation.y += Math.PI;
         meshes[0].rotation.y += Math.PI;
 
 
-        this.setupCamera(meshes);
-        this.setupLights(meshes);
-
         return; //this.initEnvironment(meshes);
         return; //this.initEnvironment(meshes);
     }
     }
 
 
-    private setModelMetaData() {
-        let navbar = this.templateManager.getTemplate('navBar');
-        if (!navbar) return;
-
-        let metadataContainer = navbar.parent.querySelector('#model-metadata');
-
-        //title
-        if (metadataContainer && typeof this.configuration.model === 'object') {
-            if (this.configuration.model.title) {
-                let element = metadataContainer.querySelector('span.model-title');
-                if (element) {
-                    element.innerHTML = this.configuration.model.title;
-                }
-            }
-
-            if (this.configuration.model.subtitle) {
-                let element = metadataContainer.querySelector('span.model-subtitle');
-                if (element) {
-                    element.innerHTML = this.configuration.model.subtitle;
-                }
-            }
-
-            if (this.configuration.model.thumbnail) {
-                (<HTMLDivElement>metadataContainer.querySelector('.thumbnail')).style.backgroundImage = `url('${this.configuration.model.thumbnail}')`;
-            }
-        }
-
-    }
-
     /*protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
     /*protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
         if (this.configuration.skybox) {
         if (this.configuration.skybox) {
             // Define a general environment textue
             // Define a general environment textue
@@ -360,10 +345,9 @@ export class DefaultViewer extends AbstractViewer {
         }));
         }));
     }
     }
 
 
-    private setupLights(focusMeshes: Array<AbstractMesh> = []) {
-
-        let sceneConfig = this.configuration.scene || { defaultLight: true };
-
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+        super.configureLights(lightsConfiguration, focusMeshes);
+        console.log("flashlight", this.configuration.lab);
         // labs feature - flashlight
         // labs feature - flashlight
         if (this.configuration.lab && this.configuration.lab.flashlight) {
         if (this.configuration.lab && this.configuration.lab.flashlight) {
             let pointerPosition = BABYLON.Vector3.Zero();
             let pointerPosition = BABYLON.Vector3.Zero();
@@ -409,131 +393,5 @@ export class DefaultViewer extends AbstractViewer {
             this.scene.registerBeforeRender(updateFlashlightFunction);
             this.scene.registerBeforeRender(updateFlashlightFunction);
             this.registeredOnBeforerenderFunctions.push(updateFlashlightFunction);
             this.registeredOnBeforerenderFunctions.push(updateFlashlightFunction);
         }
         }
-
-        if (!sceneConfig.defaultLight && (this.configuration.lights && Object.keys(this.configuration.lights).length)) {
-            // remove old lights
-            this.scene.lights.forEach(l => {
-                l.dispose();
-            });
-
-            Object.keys(this.configuration.lights).forEach((name, idx) => {
-                let lightConfig = this.configuration.lights && this.configuration.lights[name] || { name: name, type: 0 };
-                lightConfig.name = name;
-                let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
-                if (!constructor) return;
-                let light = constructor();
-
-                //enabled
-                if (light.isEnabled() !== !lightConfig.disabled) {
-                    light.setEnabled(!lightConfig.disabled);
-                }
-
-                this.extendClassWithConfig(light, lightConfig);
-
-                //position. Some lights don't support shadows
-                if (light instanceof ShadowLight) {
-                    if (lightConfig.shadowEnabled && this.maxShadows) {
-                        var shadowGenerator = new ShadowGenerator(512, light);
-                        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++) {
-                            renderList && renderList.push(focusMeshes[index]);
-                        }
-                    }
-                }
-            });
-        }
-    }
-
-    private setupCamera(focusMeshes: Array<AbstractMesh> = []) {
-
-        let cameraConfig = this.configuration.camera || {};
-        let sceneConfig = this.configuration.scene || { autoRotate: false, defaultCamera: true };
-
-        if (!this.configuration.camera && sceneConfig.defaultCamera) {
-            if (sceneConfig.autoRotate) {
-                this.camera.useAutoRotationBehavior = true;
-            }
-            return;
-        }
-
-        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.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);
-            }
-        };
-
-        if (sceneConfig.autoRotate) {
-            this.camera.useAutoRotationBehavior = true;
-        }
-
-        const sceneExtends = this.scene.getWorldExtends();
-        const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
-        const sceneDiagonalLenght = sceneDiagonal.length();
-        this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
-    }
-
-    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:
-                behavior = new AutoRotationBehavior();
-                break;
-            case CameraBehavior.BOUNCING:
-                behavior = new BouncingBehavior();
-                break;
-            case CameraBehavior.FRAMING:
-                behavior = new FramingBehavior();
-                break;
-            default:
-                behavior = null;
-                break;
-        }
-
-        if (behavior) {
-            if (typeof behaviorConfig === "object") {
-                this.extendClassWithConfig(behavior, behaviorConfig);
-            }
-            this.camera.addBehavior(behavior);
-        }
-
-        // 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;
-        }
     }
     }
 }
 }

+ 493 - 155
Viewer/src/viewer/viewer.ts

@@ -1,8 +1,11 @@
 import { viewerManager } from './viewerManager';
 import { viewerManager } from './viewerManager';
 import { TemplateManager } from './../templateManager';
 import { TemplateManager } from './../templateManager';
 import configurationLoader from './../configuration/loader';
 import configurationLoader from './../configuration/loader';
-import { CubeTexture, Color3, IEnvironmentHelperOptions, EnvironmentHelper, Effect, SceneOptimizer, SceneOptimizerOptions, Observable, Engine, Scene, ArcRotateCamera, Vector3, SceneLoader, AbstractMesh, Mesh, HemisphericLight, Database, SceneLoaderProgressEvent, ISceneLoaderPlugin, ISceneLoaderPluginAsync } from 'babylonjs';
-import { ViewerConfiguration } from '../configuration/configuration';
+import { 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 } from 'babylonjs';
+import { ViewerConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration, IObserversConfiguration, IModelConfiguration, ISkyboxConfiguration, IGroundConfiguration, ILightConfiguration, ICameraConfiguration } from '../configuration/configuration';
+
+import * as deepmerge from '../../assets/deepmerge.min.js';
+import { CameraBehavior } from 'src/interfaces';
 
 
 export abstract class AbstractViewer {
 export abstract class AbstractViewer {
 
 
@@ -10,6 +13,7 @@ export abstract class AbstractViewer {
 
 
     public engine: Engine;
     public engine: Engine;
     public scene: Scene;
     public scene: Scene;
+    public camera: ArcRotateCamera;
     public sceneOptimizer: SceneOptimizer;
     public sceneOptimizer: SceneOptimizer;
     public baseId: string;
     public baseId: string;
 
 
@@ -35,6 +39,7 @@ export abstract class AbstractViewer {
     public onEngineInitObservable: Observable<Engine>;
     public onEngineInitObservable: Observable<Engine>;
     public onModelLoadedObservable: Observable<AbstractMesh[]>;
     public onModelLoadedObservable: Observable<AbstractMesh[]>;
     public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
     public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    public onModelLoadErrorObservable: Observable<{ message: string; exception: any }>;
     public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
     public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
     public onInitDoneObservable: Observable<AbstractViewer>;
     public onInitDoneObservable: Observable<AbstractViewer>;
 
 
@@ -54,6 +59,7 @@ export abstract class AbstractViewer {
         this.onEngineInitObservable = new Observable();
         this.onEngineInitObservable = new Observable();
         this.onModelLoadedObservable = new Observable();
         this.onModelLoadedObservable = new Observable();
         this.onModelLoadProgressObservable = new Observable();
         this.onModelLoadProgressObservable = new Observable();
+        this.onModelLoadErrorObservable = new Observable();
         this.onInitDoneObservable = new Observable();
         this.onInitDoneObservable = new Observable();
         this.onLoaderInitObservable = new Observable();
         this.onLoaderInitObservable = new Observable();
 
 
@@ -70,19 +76,10 @@ export abstract class AbstractViewer {
         // extend the configuration
         // extend the configuration
         configurationLoader.loadConfiguration(initialConfiguration).then((configuration) => {
         configurationLoader.loadConfiguration(initialConfiguration).then((configuration) => {
             this.configuration = configuration;
             this.configuration = configuration;
-
-            // adding preconfigured functions
             if (this.configuration.observers) {
             if (this.configuration.observers) {
-                if (this.configuration.observers.onEngineInit) {
-                    this.onEngineInitObservable.add(window[this.configuration.observers.onEngineInit]);
-                }
-                if (this.configuration.observers.onSceneInit) {
-                    this.onSceneInitObservable.add(window[this.configuration.observers.onSceneInit]);
-                }
-                if (this.configuration.observers.onModelLoaded) {
-                    this.onModelLoadedObservable.add(window[this.configuration.observers.onModelLoaded]);
-                }
+                this.configureObservers(this.configuration.observers);
             }
             }
+            //this.updateConfiguration(configuration);
 
 
             // initialize the templates
             // initialize the templates
             let templateConfiguration = this.configuration.templates || {};
             let templateConfiguration = this.configuration.templates || {};
@@ -123,7 +120,415 @@ export abstract class AbstractViewer {
     }
     }
 
 
     protected render = (): void => {
     protected render = (): void => {
-        this.scene && this.scene.render();
+        this.scene && this.scene.activeCamera && this.scene.render();
+    }
+
+    /**
+     * Update the current viewer configuration with new values.
+     * Only provided information will be updated, old configuration values will be kept.
+     * If this.configuration was manually changed, you can trigger this function with no parameters, 
+     * and the entire configuration will be updated. 
+     * @param newConfiguration 
+     */
+    public updateConfiguration(newConfiguration: Partial<ViewerConfiguration> = this.configuration) {
+        // update scene configuration
+        if (newConfiguration.scene) {
+            this.configureScene(newConfiguration.scene);
+        }
+        // optimizer
+        if (newConfiguration.optimizer) {
+            this.configureOptimizer(newConfiguration.optimizer);
+        }
+
+        // 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);
+        }
+
+        // update this.configuration with the new data
+        this.configuration = deepmerge(this.configuration || {}, newConfiguration);
+    }
+
+    protected configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean) {
+        if (!skyboxConifguration && !groundConfiguration) {
+            if (this.environmentHelper) {
+                this.environmentHelper.dispose();
+            };
+            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) {
+                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.mirror) {
+                options.enableGroundMirror = true;
+                // to prevent undefines
+                if (typeof groundConfig.mirror === "object") {
+                    if (groundConfig.mirror.amount)
+                        options.groundMirrorAmount = groundConfig.mirror.amount;
+                    if (groundConfig.mirror.sizeRatio)
+                        options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
+                    if (groundConfig.mirror.blurKernel)
+                        options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
+                    if (groundConfig.mirror.fresnelWeight)
+                        options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
+                    if (groundConfig.mirror.fallOffDistance)
+                        options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
+                    if (this.defaultHighpTextureType !== undefined)
+                        options.groundMirrorTextureType = this.defaultHighpTextureType;
+                }
+            }
+
+        }
+
+        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;
+            }
+        }
+
+        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);
+                }
+            }
+        }
+    }
+
+    protected configureScene(sceneConfig: ISceneConfiguration, optimizerConfig?: ISceneOptimizerConfiguration) {
+        // 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;
+        }
+    }
+
+    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();
+        }
+    }
+
+    protected configureObservers(observersConfiguration: IObserversConfiguration) {
+        if (observersConfiguration.onEngineInit) {
+            this.onEngineInitObservable.add(window[observersConfiguration.onEngineInit]);
+        } else {
+            if (observersConfiguration.onEngineInit === '' && this.configuration.observers && this.configuration.observers!.onEngineInit) {
+                this.onEngineInitObservable.removeCallback(window[this.configuration.observers!.onEngineInit!]);
+            }
+        }
+        if (observersConfiguration.onSceneInit) {
+            this.onSceneInitObservable.add(window[observersConfiguration.onSceneInit]);
+        } else {
+            if (observersConfiguration.onSceneInit === '' && this.configuration.observers && this.configuration.observers!.onSceneInit) {
+                this.onSceneInitObservable.removeCallback(window[this.configuration.observers!.onSceneInit!]);
+            }
+        }
+        if (observersConfiguration.onModelLoaded) {
+            this.onModelLoadedObservable.add(window[observersConfiguration.onModelLoaded]);
+        } else {
+            if (observersConfiguration.onModelLoaded === '' && this.configuration.observers && this.configuration.observers!.onModelLoaded) {
+                this.onModelLoadedObservable.removeCallback(window[this.configuration.observers!.onModelLoaded!]);
+            }
+        }
+    }
+
+    protected configureCamera(cameraConfig: ICameraConfiguration, focusMeshes: Array<AbstractMesh> = 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.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();
+        const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
+        const sceneDiagonalLenght = sceneDiagonal.length();
+        this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
+    }
+
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+        // sanity check!
+        if (!Object.keys(lightsConfiguration).length) return;
+
+        let lightsAvailable: Array<string> = this.scene.lights.map(light => light.name);
+
+        Object.keys(lightsConfiguration).forEach((name, idx) => {
+            let lightConfig: ILightConfiguration = { type: 0 };
+            if (typeof lightsConfiguration[name] === 'object') {
+                lightConfig = <ILightConfiguration>lightsConfiguration[name];
+            }
+
+            lightConfig.name = name;
+
+            let light;
+            // light 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 = this.scene.getLightByName(name);
+                lightsAvailable = lightsAvailable.filter(ln => ln !== name);
+            }
+
+            // if config set the light to false, dispose it.
+            if (lightsConfiguration[name] === false) {
+                light.dispose();
+                return;
+            }
+
+            //enabled
+            if (light.isEnabled() !== !lightConfig.disabled) {
+                light.setEnabled(!lightConfig.disabled);
+            }
+
+            this.extendClassWithConfig(light, lightConfig);
+
+            //position. Some lights don't support shadows
+            if (light instanceof ShadowLight) {
+                let shadowGenerator = light.getShadowGenerator();
+                if (lightConfig.shadowEnabled && this.maxShadows) {
+                    if (!shadowGenerator) {
+                        shadowGenerator = new ShadowGenerator(512, light);
+                    }
+                    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();
+                }
+            }
+        });
+
+        // remove the unneeded lights
+        /*lightsAvailable.forEach(name => {
+            let light = this.scene.getLightByName(name);
+            if (light) {
+                light.dispose();
+            }
+        });*/
+    }
+
+    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+        let meshesWithNoParent: Array<AbstractMesh> = focusMeshes.filter(m => !m.parent);
+        let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
+            meshesWithNoParent.forEach(mesh => {
+                if (param) {
+                    mesh[variable][param] = value;
+                } else {
+                    mesh[variable] = value;
+                }
+            });
+        }
+        let updateXYZ = (variable: string, configValues: { x: number, y: number, z: number, w?: number }) => {
+            if (configValues.x !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.x, 'x');
+            }
+            if (configValues.y !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.y, 'y');
+            }
+            if (configValues.z !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.z, 'z');
+            }
+            if (configValues.w !== undefined) {
+                updateMeshesWithNoParent(variable, configValues.w, 'w');
+            }
+        }
+        // position?
+        if (modelConfiguration.position) {
+            updateXYZ('position', modelConfiguration.position);
+        }
+        if (modelConfiguration.rotation) {
+            if (modelConfiguration.rotation.w) {
+                meshesWithNoParent.forEach(mesh => {
+                    if (!mesh.rotationQuaternion) {
+                        mesh.rotationQuaternion = new Quaternion();
+                    }
+                })
+                updateXYZ('rotationQuaternion', modelConfiguration.rotation);
+            } else {
+                updateXYZ('rotation', modelConfiguration.rotation);
+            }
+        }
+        if (modelConfiguration.scaling) {
+            updateXYZ('scaling', modelConfiguration.scaling);
+        }
+
+        if (modelConfiguration.castShadow) {
+            focusMeshes.forEach(mesh => {
+                Tags.AddTagsTo(mesh, 'castShadow');
+            });
+        }
     }
     }
 
 
     public dispose() {
     public dispose() {
@@ -233,48 +638,17 @@ export abstract class AbstractViewer {
         // create a new scene
         // create a new scene
         this.scene = new Scene(this.engine);
         this.scene = new Scene(this.engine);
         // make sure there is a default camera and light.
         // make sure there is a default camera and light.
-        this.scene.createDefaultCameraOrLight(true, true, true);
+        this.scene.createDefaultLight(true);
+
         if (this.configuration.scene) {
         if (this.configuration.scene) {
-            if (this.configuration.scene.debug) {
-                this.scene.debugLayer.show();
-            }
+            this.configureScene(this.configuration.scene);
 
 
             // Scene optimizer
             // Scene optimizer
             if (this.configuration.optimizer) {
             if (this.configuration.optimizer) {
-
-                let optimizerConfig = this.configuration.optimizer;
-                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;
-                    }
-                }
-
-                this.sceneOptimizer = new SceneOptimizer(this.scene, optimizerOptions, optimizerConfig.autoGeneratePriorities, optimizerConfig.improvementMode);
-                this.sceneOptimizer.start();
-            }
-
-            // image processing configuration - optional.
-            if (this.configuration.scene.imageProcessingConfiguration) {
-                this.extendClassWithConfig(this.scene.imageProcessingConfiguration, this.configuration.scene.imageProcessingConfiguration);
-            }
-            if (this.configuration.scene.environmentTexture) {
-                const environmentTexture = CubeTexture.CreateFromPrefilteredData(this.configuration.scene.environmentTexture, this.scene);
-                this.scene.environmentTexture = environmentTexture;
+                this.configureOptimizer(this.configuration.optimizer);
             }
             }
         }
         }
 
 
-
-
         return Promise.resolve(this.scene);
         return Promise.resolve(this.scene);
     }
     }
 
 
@@ -287,8 +661,14 @@ export abstract class AbstractViewer {
         let plugin = (typeof model === 'string') ? undefined : model.loader;
         let plugin = (typeof model === 'string') ? undefined : model.loader;
 
 
         return Promise.resolve(this.scene).then((scene) => {
         return Promise.resolve(this.scene).then((scene) => {
-            if (!scene || clearScene) return this.initScene();
-            else return this.scene!;
+            if (!scene) return this.initScene();
+
+            if (clearScene) {
+                scene.meshes.forEach(mesh => {
+                    mesh.dispose();
+                });
+            }
+            return scene!;
         }).then(() => {
         }).then(() => {
             return new Promise<Array<AbstractMesh>>((resolve, reject) => {
             return new Promise<Array<AbstractMesh>>((resolve, reject) => {
                 this.lastUsedLoader = SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
                 this.lastUsedLoader = SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
@@ -297,14 +677,23 @@ export abstract class AbstractViewer {
                     this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
                     this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
                 }, (e, m, exception) => {
                 }, (e, m, exception) => {
                     // console.log(m, exception);
                     // console.log(m, exception);
-                    reject(m);
+                    this.onModelLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception }).then(() => {
+                        reject(exception);
+                    });
                 }, plugin)!;
                 }, plugin)!;
                 this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
                 this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
             });
             });
         }).then((meshes: Array<AbstractMesh>) => {
         }).then((meshes: Array<AbstractMesh>) => {
             return this.onModelLoadedObservable.notifyObserversWithPromise(meshes)
             return this.onModelLoadedObservable.notifyObserversWithPromise(meshes)
                 .then(() => {
                 .then(() => {
-                    this.initEnvironment();
+                    // update the models' configuration
+                    this.configureModel(model, meshes);
+                    this.configureLights(this.configuration.lights);
+
+                    if (this.configuration.camera) {
+                        this.configureCamera(this.configuration.camera, meshes);
+                    }
+                    return this.initEnvironment(meshes);
                 }).then(() => {
                 }).then(() => {
                     return this.scene;
                     return this.scene;
                 });
                 });
@@ -312,108 +701,7 @@ export abstract class AbstractViewer {
     }
     }
 
 
     protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
     protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
-        if (!this.configuration.skybox && !this.configuration.ground) {
-            if (this.environmentHelper) {
-                this.environmentHelper.dispose();
-            };
-            return Promise.resolve(this.scene);
-        }
-
-        const options: Partial<IEnvironmentHelperOptions> = {
-            createGround: !!this.configuration.ground,
-            createSkybox: !!this.configuration.skybox,
-            setupImageProcessing: false // will be done at the scene level!
-        };
-
-        if (this.configuration.ground) {
-            let groundConfig = (typeof this.configuration.ground === 'boolean') ? {} : this.configuration.ground;
-
-            let groundSize = groundConfig.size || (this.configuration.skybox && this.configuration.skybox.scale);
-            if (groundSize) {
-                options.groundSize = groundSize;
-            }
-
-            options.enableGroundShadow = this.configuration.ground === true || groundConfig.receiveShadows;
-            if (groundConfig.shadowLevel) {
-                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.mirror) {
-                options.enableGroundMirror = true;
-                // to prevent undefines
-                if (typeof groundConfig.mirror === "object") {
-                    if (groundConfig.mirror.amount)
-                        options.groundMirrorAmount = groundConfig.mirror.amount;
-                    if (groundConfig.mirror.sizeRatio)
-                        options.groundMirrorSizeRatio = groundConfig.mirror.sizeRatio;
-                    if (groundConfig.mirror.blurKernel)
-                        options.groundMirrorBlurKernel = groundConfig.mirror.blurKernel;
-                    if (groundConfig.mirror.fresnelWeight)
-                        options.groundMirrorFresnelWeight = groundConfig.mirror.fresnelWeight;
-                    if (groundConfig.mirror.fallOffDistance)
-                        options.groundMirrorFallOffDistance = groundConfig.mirror.fallOffDistance;
-                    if (this.defaultHighpTextureType !== undefined)
-                        options.groundMirrorTextureType = this.defaultHighpTextureType;
-                }
-            }
-
-        }
-
-        let postInitSkyboxMaterial = false;
-        if (this.configuration.skybox) {
-            let conf = this.configuration.skybox;
-            if (conf.material && conf.material.imageProcessingConfiguration) {
-                options.setupImageProcessing = false; // will be configured later manually.
-            }
-            let skyboxSize = this.configuration.skybox.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;
-            }
-        }
-
-        if (!this.environmentHelper) {
-            this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
-        }
-        else {
-            // there might be a new scene! we need to dispose.
-            // Need to decide if a scene should stay or be disposed.
-            this.environmentHelper.dispose();
-            //this.environmentHelper.updateOptions(options);
-            this.environmentHelper = this.scene.createDefaultEnvironment(options)!;
-        }
-        console.log(options);
-
-        if (postInitSkyboxMaterial) {
-            let skyboxMaterial = this.environmentHelper.skyboxMaterial;
-            if (skyboxMaterial) {
-                if (this.configuration.skybox && this.configuration.skybox.material && this.configuration.skybox.material.imageProcessingConfiguration) {
-                    this.extendClassWithConfig(skyboxMaterial.imageProcessingConfiguration, this.configuration.skybox.material.imageProcessingConfiguration);
-                }
-            }
-        }
+        this.configureEnvironment(this.configuration.skybox, this.configuration.ground);
 
 
         return Promise.resolve(this.scene);
         return Promise.resolve(this.scene);
     }
     }
@@ -492,4 +780,54 @@ export abstract class AbstractViewer {
             }
             }
         });
         });
     }
     }
+
+    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:
+                behavior = new AutoRotationBehavior();
+                break;
+            case CameraBehavior.BOUNCING:
+                behavior = new BouncingBehavior();
+                break;
+            case CameraBehavior.FRAMING:
+                behavior = new FramingBehavior();
+                break;
+            default:
+                behavior = null;
+                break;
+        }
+
+        if (behavior) {
+            if (typeof behaviorConfig === "object") {
+                this.extendClassWithConfig(behavior, behaviorConfig);
+            }
+            this.camera.addBehavior(behavior);
+        }
+
+        // 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;
+        }
+    }
 }
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 9270 - 9187
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 17 - 17
dist/preview release/babylon.js


+ 123 - 45
dist/preview release/babylon.max.js

@@ -6564,12 +6564,24 @@ var BABYLON;
      */
      */
     var EventState = /** @class */ (function () {
     var EventState = /** @class */ (function () {
         /**
         /**
-        * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
-        */
+         * Create a new EventState
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         */
         function EventState(mask, skipNextObservers, target, currentTarget) {
         function EventState(mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.initalize(mask, skipNextObservers, target, currentTarget);
             this.initalize(mask, skipNextObservers, target, currentTarget);
         }
         }
+        /**
+         * Initialize the current event state
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns the current event state
+         */
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.mask = mask;
             this.mask = mask;
@@ -6585,11 +6597,33 @@ var BABYLON;
      * Represent an Observer registered to a given Observable object.
      * Represent an Observer registered to a given Observable object.
      */
      */
     var Observer = /** @class */ (function () {
     var Observer = /** @class */ (function () {
-        function Observer(callback, mask, scope) {
+        /**
+         * Creates a new observer
+         * @param callback defines the callback to call when the observer is notified
+         * @param mask defines the mask of the observer (used to filter notifications)
+         * @param scope defines the current scope used to restore the JS context
+         */
+        function Observer(
+            /**
+             * Defines the callback to call when the observer is notified
+             */
+            callback, 
+            /**
+             * Defines the mask of the observer (used to filter notifications)
+             */
+            mask, 
+            /**
+             * Defines the current scope used to restore the JS context
+             */
+            scope) {
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
             this.callback = callback;
             this.callback = callback;
             this.mask = mask;
             this.mask = mask;
             this.scope = scope;
             this.scope = scope;
+            /**
+             * Gets or sets a property defining that the observer as to be unregistered after the next notification
+             */
+            this.unregisterOnNextCall = false;
         }
         }
         return Observer;
         return Observer;
     }());
     }());
@@ -6600,6 +6634,9 @@ var BABYLON;
     var MultiObserver = /** @class */ (function () {
     var MultiObserver = /** @class */ (function () {
         function MultiObserver() {
         function MultiObserver() {
         }
         }
+        /**
+         * Release associated resources
+         */
         MultiObserver.prototype.dispose = function () {
         MultiObserver.prototype.dispose = function () {
             if (this._observers && this._observables) {
             if (this._observers && this._observables) {
                 for (var index = 0; index < this._observers.length; index++) {
                 for (var index = 0; index < this._observers.length; index++) {
@@ -6609,6 +6646,14 @@ var BABYLON;
             this._observers = null;
             this._observers = null;
             this._observables = null;
             this._observables = null;
         };
         };
+        /**
+         * Raise a callback when one of the observable will notify
+         * @param observables defines a list of observables to watch
+         * @param callback defines the callback to call on notification
+         * @param mask defines the mask used to filter notifications
+         * @param scope defines the current scope used to restore the JS context
+         * @returns the new MultiObserver
+         */
         MultiObserver.Watch = function (observables, callback, mask, scope) {
         MultiObserver.Watch = function (observables, callback, mask, scope) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
@@ -6635,6 +6680,10 @@ var BABYLON;
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
      */
     var Observable = /** @class */ (function () {
     var Observable = /** @class */ (function () {
+        /**
+         * Creates a new observable
+         * @param onObserverAdded defines a callback to call when a new observer is added
+         */
         function Observable(onObserverAdded) {
         function Observable(onObserverAdded) {
             this._observers = new Array();
             this._observers = new Array();
             this._eventState = new EventState(0);
             this._eventState = new EventState(0);
@@ -6648,15 +6697,19 @@ var BABYLON;
          * @param mask the mask used to filter observers
          * @param mask the mask used to filter observers
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param scope optional scope for the callback to be called from
          * @param scope optional scope for the callback to be called from
+         * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
+         * @returns the new observer created for the callback
          */
          */
-        Observable.prototype.add = function (callback, mask, insertFirst, scope) {
+        Observable.prototype.add = function (callback, mask, insertFirst, scope, unregisterOnFirstCall) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
+            if (unregisterOnFirstCall === void 0) { unregisterOnFirstCall = false; }
             if (!callback) {
             if (!callback) {
                 return null;
                 return null;
             }
             }
             var observer = new Observer(callback, mask, scope);
             var observer = new Observer(callback, mask, scope);
+            observer.unregisterOnNextCall = unregisterOnFirstCall;
             if (insertFirst) {
             if (insertFirst) {
                 this._observers.unshift(observer);
                 this._observers.unshift(observer);
             }
             }
@@ -6670,7 +6723,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove an Observer from the Observable object
          * Remove an Observer from the Observable object
-         * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
+         * @param observer the instance of the Observer to remove
+         * @returns false if it doesn't belong to this Observable
          */
          */
         Observable.prototype.remove = function (observer) {
         Observable.prototype.remove = function (observer) {
             if (!observer) {
             if (!observer) {
@@ -6685,8 +6739,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove a callback from the Observable object
          * Remove a callback from the Observable object
-         * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
-         * @param scope optional scope. If used only the callbacks with this scope will be removed.
+         * @param callback the callback to remove
+         * @param scope optional scope. If used only the callbacks with this scope will be removed
+         * @returns false if it doesn't belong to this Observable
         */
         */
         Observable.prototype.removeCallback = function (callback, scope) {
         Observable.prototype.removeCallback = function (callback, scope) {
             for (var index = 0; index < this._observers.length; index++) {
             for (var index = 0; index < this._observers.length; index++) {
@@ -6697,11 +6752,20 @@ var BABYLON;
             }
             }
             return false;
             return false;
         };
         };
+        Observable.prototype._deferUnregister = function (observer) {
+            var _this = this;
+            BABYLON.Tools.SetImmediate(function () {
+                _this.remove(observer);
+            });
+        };
         /**
         /**
          * Notify all Observers by calling their respective callback with the given data
          * Notify all Observers by calling their respective callback with the given data
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
-         * @param eventData
-         * @param mask
+         * @param eventData defines the data to send to all observers
+         * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
          */
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6723,6 +6787,9 @@ var BABYLON;
                     else {
                     else {
                         state.lastReturnValue = obs.callback(eventData, state);
                         state.lastReturnValue = obs.callback(eventData, state);
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }
                 }
                 }
                 if (state.skipNextObservers) {
                 if (state.skipNextObservers) {
                     return false;
                     return false;
@@ -6739,11 +6806,12 @@ var BABYLON;
          *
          *
          * @param eventData The data to be sent to each callback
          * @param eventData The data to be sent to each callback
          * @param mask is used to filter observers defaults to -1
          * @param mask is used to filter observers defaults to -1
-         * @param target the callback target (see EventState)
-         * @param currentTarget The current object in the bubbling phase
+         * @param target defines the callback target (see EventState)
+         * @param currentTarget defines he current object in the bubbling phase
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          */
          */
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
+            var _this = this;
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             // create an empty promise
             // create an empty promise
             var p = Promise.resolve(eventData);
             var p = Promise.resolve(eventData);
@@ -6774,6 +6842,9 @@ var BABYLON;
                             return obs.callback(eventData, state);
                             return obs.callback(eventData, state);
                         });
                         });
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        _this._deferUnregister(obs);
+                    }
                 }
                 }
             });
             });
             // return the eventData
             // return the eventData
@@ -6781,8 +6852,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Notify a specific observer
          * Notify a specific observer
-         * @param eventData
-         * @param mask
+         * @param observer defines the observer to notify
+         * @param eventData defines the data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
          */
          */
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6792,7 +6864,8 @@ var BABYLON;
             observer.callback(eventData, state);
             observer.callback(eventData, state);
         };
         };
         /**
         /**
-         * return true is the Observable has at least one Observer registered
+         * Gets a boolean indicating if the observable has at least one observer
+         * @returns true is the Observable has at least one Observer registered
          */
          */
         Observable.prototype.hasObservers = function () {
         Observable.prototype.hasObservers = function () {
             return this._observers.length > 0;
             return this._observers.length > 0;
@@ -6805,8 +6878,9 @@ var BABYLON;
             this._onObserverAdded = null;
             this._onObserverAdded = null;
         };
         };
         /**
         /**
-        * Clone the current observable
-        */
+         * Clone the current observable
+         * @returns a new observable
+         */
         Observable.prototype.clone = function () {
         Observable.prototype.clone = function () {
             var result = new Observable();
             var result = new Observable();
             result._observers = this._observers.slice(0);
             result._observers = this._observers.slice(0);
@@ -6814,8 +6888,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Does this observable handles observer registered with a given mask
          * Does this observable handles observer registered with a given mask
-         * @param {number} trigger - the mask to be tested
-         * @return {boolean} whether or not one observer registered with the given mask is handeled
+         * @param mask defines the mask to be tested
+         * @return whether or not one observer registered with the given mask is handeled
         **/
         **/
         Observable.prototype.hasSpecificMask = function (mask) {
         Observable.prototype.hasSpecificMask = function (mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -15195,19 +15269,31 @@ var BABYLON;
             configurable: true
             configurable: true
         });
         });
         /**
         /**
-         * Sets a new pivot matrix to the mesh.
-         * Returns the AbstractMesh.
+         * Sets a new matrix to apply before all other transformation
+         * @param matrix defines the transform matrix
+         * @returns the current TransformNode
+         */
+        TransformNode.prototype.setPreTransformMatrix = function (matrix) {
+            return this.setPivotMatrix(matrix, false);
+        };
+        /**
+         * Sets a new pivot matrix to the current node
+         * @param matrix defines the new pivot matrix to use
+         * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
+         * @returns the current TransformNode
         */
         */
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
-            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = false; }
+            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = true; }
             this._pivotMatrix = matrix.clone();
             this._pivotMatrix = matrix.clone();
             this._cache.pivotMatrixUpdated = true;
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+            if (this._postMultiplyPivotMatrix) {
+                if (!this._pivotMatrixInverse) {
+                    this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
+                }
+                else {
+                    this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+                }
             }
             }
             return this;
             return this;
         };
         };
@@ -15371,6 +15457,12 @@ var BABYLON;
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             return this;
             return this;
         };
         };
+        /**
+         * Sets a new pivot point to the current node
+         * @param point defines the new pivot point to use
+         * @param space defines if the point is in world or local space (local by default)
+         * @returns the current TransformNode
+        */
         TransformNode.prototype.setPivotPoint = function (point, space) {
         TransformNode.prototype.setPivotPoint = function (point, space) {
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (this.getScene().getRenderId() == 0) {
             if (this.getScene().getRenderId() == 0) {
@@ -15382,18 +15474,7 @@ var BABYLON;
                 wm.invertToRef(tmat);
                 wm.invertToRef(tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
             }
             }
-            BABYLON.Vector3.TransformCoordinatesToRef(point, wm, this.position);
-            this._pivotMatrix.m[12] = -point.x;
-            this._pivotMatrix.m[13] = -point.y;
-            this._pivotMatrix.m[14] = -point.z;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
-            }
-            this._cache.pivotMatrixUpdated = true;
-            return this;
+            return this.setPivotMatrix(BABYLON.Matrix.Translation(point.x, point.y, point.z), true);
         };
         };
         /**
         /**
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
@@ -15764,9 +15845,6 @@ var BABYLON;
             this._afterComputeWorldMatrix();
             this._afterComputeWorldMatrix();
             // Absolute position
             // Absolute position
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
-            if (this._pivotMatrixInverse) {
-                BABYLON.Vector3.TransformCoordinatesToRef(this._absolutePosition, this._pivotMatrixInverse, this._absolutePosition);
-            }
             // Callbacks
             // Callbacks
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             if (!this._poseMatrix) {
             if (!this._poseMatrix) {
@@ -15853,7 +15931,7 @@ var BABYLON;
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
             }
             }
             if (parsedTransformNode.localMatrix) {
             if (parsedTransformNode.localMatrix) {
-                transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
+                transformNode.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
             }
             }
             else if (parsedTransformNode.pivotMatrix) {
             else if (parsedTransformNode.pivotMatrix) {
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
@@ -27966,7 +28044,7 @@ var BABYLON;
             }
             }
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             if (parsedMesh.localMatrix) {
             if (parsedMesh.localMatrix) {
-                mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
+                mesh.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
             }
             }
             else if (parsedMesh.pivotMatrix) {
             else if (parsedMesh.pivotMatrix) {
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
@@ -73821,7 +73899,7 @@ var BABYLON;
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
-                mesh.setPivotMatrix(p);
+                mesh.setPreTransformMatrix(p);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
                 //calculate the translation
                 //calculate the translation
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
@@ -73831,7 +73909,7 @@ var BABYLON;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 //rotation is back
                 //rotation is back
                 mesh.rotationQuaternion = rotationQuaternion;
                 mesh.rotationQuaternion = rotationQuaternion;
-                mesh.setPivotMatrix(oldPivot);
+                mesh.setPreTransformMatrix(oldPivot);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
             }
             }
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 17 - 17
dist/preview release/babylon.worker.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 3509 - 3426
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 17 - 17
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


+ 123 - 45
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js

@@ -6564,12 +6564,24 @@ var BABYLON;
      */
      */
     var EventState = /** @class */ (function () {
     var EventState = /** @class */ (function () {
         /**
         /**
-        * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
-        */
+         * Create a new EventState
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         */
         function EventState(mask, skipNextObservers, target, currentTarget) {
         function EventState(mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.initalize(mask, skipNextObservers, target, currentTarget);
             this.initalize(mask, skipNextObservers, target, currentTarget);
         }
         }
+        /**
+         * Initialize the current event state
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns the current event state
+         */
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.mask = mask;
             this.mask = mask;
@@ -6585,11 +6597,33 @@ var BABYLON;
      * Represent an Observer registered to a given Observable object.
      * Represent an Observer registered to a given Observable object.
      */
      */
     var Observer = /** @class */ (function () {
     var Observer = /** @class */ (function () {
-        function Observer(callback, mask, scope) {
+        /**
+         * Creates a new observer
+         * @param callback defines the callback to call when the observer is notified
+         * @param mask defines the mask of the observer (used to filter notifications)
+         * @param scope defines the current scope used to restore the JS context
+         */
+        function Observer(
+            /**
+             * Defines the callback to call when the observer is notified
+             */
+            callback, 
+            /**
+             * Defines the mask of the observer (used to filter notifications)
+             */
+            mask, 
+            /**
+             * Defines the current scope used to restore the JS context
+             */
+            scope) {
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
             this.callback = callback;
             this.callback = callback;
             this.mask = mask;
             this.mask = mask;
             this.scope = scope;
             this.scope = scope;
+            /**
+             * Gets or sets a property defining that the observer as to be unregistered after the next notification
+             */
+            this.unregisterOnNextCall = false;
         }
         }
         return Observer;
         return Observer;
     }());
     }());
@@ -6600,6 +6634,9 @@ var BABYLON;
     var MultiObserver = /** @class */ (function () {
     var MultiObserver = /** @class */ (function () {
         function MultiObserver() {
         function MultiObserver() {
         }
         }
+        /**
+         * Release associated resources
+         */
         MultiObserver.prototype.dispose = function () {
         MultiObserver.prototype.dispose = function () {
             if (this._observers && this._observables) {
             if (this._observers && this._observables) {
                 for (var index = 0; index < this._observers.length; index++) {
                 for (var index = 0; index < this._observers.length; index++) {
@@ -6609,6 +6646,14 @@ var BABYLON;
             this._observers = null;
             this._observers = null;
             this._observables = null;
             this._observables = null;
         };
         };
+        /**
+         * Raise a callback when one of the observable will notify
+         * @param observables defines a list of observables to watch
+         * @param callback defines the callback to call on notification
+         * @param mask defines the mask used to filter notifications
+         * @param scope defines the current scope used to restore the JS context
+         * @returns the new MultiObserver
+         */
         MultiObserver.Watch = function (observables, callback, mask, scope) {
         MultiObserver.Watch = function (observables, callback, mask, scope) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
@@ -6635,6 +6680,10 @@ var BABYLON;
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
      */
     var Observable = /** @class */ (function () {
     var Observable = /** @class */ (function () {
+        /**
+         * Creates a new observable
+         * @param onObserverAdded defines a callback to call when a new observer is added
+         */
         function Observable(onObserverAdded) {
         function Observable(onObserverAdded) {
             this._observers = new Array();
             this._observers = new Array();
             this._eventState = new EventState(0);
             this._eventState = new EventState(0);
@@ -6648,15 +6697,19 @@ var BABYLON;
          * @param mask the mask used to filter observers
          * @param mask the mask used to filter observers
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param scope optional scope for the callback to be called from
          * @param scope optional scope for the callback to be called from
+         * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
+         * @returns the new observer created for the callback
          */
          */
-        Observable.prototype.add = function (callback, mask, insertFirst, scope) {
+        Observable.prototype.add = function (callback, mask, insertFirst, scope, unregisterOnFirstCall) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
+            if (unregisterOnFirstCall === void 0) { unregisterOnFirstCall = false; }
             if (!callback) {
             if (!callback) {
                 return null;
                 return null;
             }
             }
             var observer = new Observer(callback, mask, scope);
             var observer = new Observer(callback, mask, scope);
+            observer.unregisterOnNextCall = unregisterOnFirstCall;
             if (insertFirst) {
             if (insertFirst) {
                 this._observers.unshift(observer);
                 this._observers.unshift(observer);
             }
             }
@@ -6670,7 +6723,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove an Observer from the Observable object
          * Remove an Observer from the Observable object
-         * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
+         * @param observer the instance of the Observer to remove
+         * @returns false if it doesn't belong to this Observable
          */
          */
         Observable.prototype.remove = function (observer) {
         Observable.prototype.remove = function (observer) {
             if (!observer) {
             if (!observer) {
@@ -6685,8 +6739,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove a callback from the Observable object
          * Remove a callback from the Observable object
-         * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
-         * @param scope optional scope. If used only the callbacks with this scope will be removed.
+         * @param callback the callback to remove
+         * @param scope optional scope. If used only the callbacks with this scope will be removed
+         * @returns false if it doesn't belong to this Observable
         */
         */
         Observable.prototype.removeCallback = function (callback, scope) {
         Observable.prototype.removeCallback = function (callback, scope) {
             for (var index = 0; index < this._observers.length; index++) {
             for (var index = 0; index < this._observers.length; index++) {
@@ -6697,11 +6752,20 @@ var BABYLON;
             }
             }
             return false;
             return false;
         };
         };
+        Observable.prototype._deferUnregister = function (observer) {
+            var _this = this;
+            BABYLON.Tools.SetImmediate(function () {
+                _this.remove(observer);
+            });
+        };
         /**
         /**
          * Notify all Observers by calling their respective callback with the given data
          * Notify all Observers by calling their respective callback with the given data
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
-         * @param eventData
-         * @param mask
+         * @param eventData defines the data to send to all observers
+         * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
          */
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6723,6 +6787,9 @@ var BABYLON;
                     else {
                     else {
                         state.lastReturnValue = obs.callback(eventData, state);
                         state.lastReturnValue = obs.callback(eventData, state);
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }
                 }
                 }
                 if (state.skipNextObservers) {
                 if (state.skipNextObservers) {
                     return false;
                     return false;
@@ -6739,11 +6806,12 @@ var BABYLON;
          *
          *
          * @param eventData The data to be sent to each callback
          * @param eventData The data to be sent to each callback
          * @param mask is used to filter observers defaults to -1
          * @param mask is used to filter observers defaults to -1
-         * @param target the callback target (see EventState)
-         * @param currentTarget The current object in the bubbling phase
+         * @param target defines the callback target (see EventState)
+         * @param currentTarget defines he current object in the bubbling phase
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          */
          */
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
+            var _this = this;
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             // create an empty promise
             // create an empty promise
             var p = Promise.resolve(eventData);
             var p = Promise.resolve(eventData);
@@ -6774,6 +6842,9 @@ var BABYLON;
                             return obs.callback(eventData, state);
                             return obs.callback(eventData, state);
                         });
                         });
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        _this._deferUnregister(obs);
+                    }
                 }
                 }
             });
             });
             // return the eventData
             // return the eventData
@@ -6781,8 +6852,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Notify a specific observer
          * Notify a specific observer
-         * @param eventData
-         * @param mask
+         * @param observer defines the observer to notify
+         * @param eventData defines the data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
          */
          */
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6792,7 +6864,8 @@ var BABYLON;
             observer.callback(eventData, state);
             observer.callback(eventData, state);
         };
         };
         /**
         /**
-         * return true is the Observable has at least one Observer registered
+         * Gets a boolean indicating if the observable has at least one observer
+         * @returns true is the Observable has at least one Observer registered
          */
          */
         Observable.prototype.hasObservers = function () {
         Observable.prototype.hasObservers = function () {
             return this._observers.length > 0;
             return this._observers.length > 0;
@@ -6805,8 +6878,9 @@ var BABYLON;
             this._onObserverAdded = null;
             this._onObserverAdded = null;
         };
         };
         /**
         /**
-        * Clone the current observable
-        */
+         * Clone the current observable
+         * @returns a new observable
+         */
         Observable.prototype.clone = function () {
         Observable.prototype.clone = function () {
             var result = new Observable();
             var result = new Observable();
             result._observers = this._observers.slice(0);
             result._observers = this._observers.slice(0);
@@ -6814,8 +6888,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Does this observable handles observer registered with a given mask
          * Does this observable handles observer registered with a given mask
-         * @param {number} trigger - the mask to be tested
-         * @return {boolean} whether or not one observer registered with the given mask is handeled
+         * @param mask defines the mask to be tested
+         * @return whether or not one observer registered with the given mask is handeled
         **/
         **/
         Observable.prototype.hasSpecificMask = function (mask) {
         Observable.prototype.hasSpecificMask = function (mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -15195,19 +15269,31 @@ var BABYLON;
             configurable: true
             configurable: true
         });
         });
         /**
         /**
-         * Sets a new pivot matrix to the mesh.
-         * Returns the AbstractMesh.
+         * Sets a new matrix to apply before all other transformation
+         * @param matrix defines the transform matrix
+         * @returns the current TransformNode
+         */
+        TransformNode.prototype.setPreTransformMatrix = function (matrix) {
+            return this.setPivotMatrix(matrix, false);
+        };
+        /**
+         * Sets a new pivot matrix to the current node
+         * @param matrix defines the new pivot matrix to use
+         * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
+         * @returns the current TransformNode
         */
         */
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
-            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = false; }
+            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = true; }
             this._pivotMatrix = matrix.clone();
             this._pivotMatrix = matrix.clone();
             this._cache.pivotMatrixUpdated = true;
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+            if (this._postMultiplyPivotMatrix) {
+                if (!this._pivotMatrixInverse) {
+                    this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
+                }
+                else {
+                    this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+                }
             }
             }
             return this;
             return this;
         };
         };
@@ -15371,6 +15457,12 @@ var BABYLON;
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             return this;
             return this;
         };
         };
+        /**
+         * Sets a new pivot point to the current node
+         * @param point defines the new pivot point to use
+         * @param space defines if the point is in world or local space (local by default)
+         * @returns the current TransformNode
+        */
         TransformNode.prototype.setPivotPoint = function (point, space) {
         TransformNode.prototype.setPivotPoint = function (point, space) {
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (this.getScene().getRenderId() == 0) {
             if (this.getScene().getRenderId() == 0) {
@@ -15382,18 +15474,7 @@ var BABYLON;
                 wm.invertToRef(tmat);
                 wm.invertToRef(tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
             }
             }
-            BABYLON.Vector3.TransformCoordinatesToRef(point, wm, this.position);
-            this._pivotMatrix.m[12] = -point.x;
-            this._pivotMatrix.m[13] = -point.y;
-            this._pivotMatrix.m[14] = -point.z;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
-            }
-            this._cache.pivotMatrixUpdated = true;
-            return this;
+            return this.setPivotMatrix(BABYLON.Matrix.Translation(point.x, point.y, point.z), true);
         };
         };
         /**
         /**
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
@@ -15764,9 +15845,6 @@ var BABYLON;
             this._afterComputeWorldMatrix();
             this._afterComputeWorldMatrix();
             // Absolute position
             // Absolute position
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
-            if (this._pivotMatrixInverse) {
-                BABYLON.Vector3.TransformCoordinatesToRef(this._absolutePosition, this._pivotMatrixInverse, this._absolutePosition);
-            }
             // Callbacks
             // Callbacks
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             if (!this._poseMatrix) {
             if (!this._poseMatrix) {
@@ -15853,7 +15931,7 @@ var BABYLON;
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
             }
             }
             if (parsedTransformNode.localMatrix) {
             if (parsedTransformNode.localMatrix) {
-                transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
+                transformNode.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
             }
             }
             else if (parsedTransformNode.pivotMatrix) {
             else if (parsedTransformNode.pivotMatrix) {
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
@@ -27966,7 +28044,7 @@ var BABYLON;
             }
             }
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             if (parsedMesh.localMatrix) {
             if (parsedMesh.localMatrix) {
-                mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
+                mesh.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
             }
             }
             else if (parsedMesh.pivotMatrix) {
             else if (parsedMesh.pivotMatrix) {
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
@@ -73563,7 +73641,7 @@ var BABYLON;
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
-                mesh.setPivotMatrix(p);
+                mesh.setPreTransformMatrix(p);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
                 //calculate the translation
                 //calculate the translation
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
@@ -73573,7 +73651,7 @@ var BABYLON;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 //rotation is back
                 //rotation is back
                 mesh.rotationQuaternion = rotationQuaternion;
                 mesh.rotationQuaternion = rotationQuaternion;
-                mesh.setPivotMatrix(oldPivot);
+                mesh.setPreTransformMatrix(oldPivot);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
             }
             }
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {

+ 123 - 45
dist/preview release/customConfigurations/minimalGLTFViewer/es6.js

@@ -6550,12 +6550,24 @@ var BABYLON;
      */
      */
     var EventState = /** @class */ (function () {
     var EventState = /** @class */ (function () {
         /**
         /**
-        * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
-        */
+         * Create a new EventState
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         */
         function EventState(mask, skipNextObservers, target, currentTarget) {
         function EventState(mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.initalize(mask, skipNextObservers, target, currentTarget);
             this.initalize(mask, skipNextObservers, target, currentTarget);
         }
         }
+        /**
+         * Initialize the current event state
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns the current event state
+         */
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.mask = mask;
             this.mask = mask;
@@ -6571,11 +6583,33 @@ var BABYLON;
      * Represent an Observer registered to a given Observable object.
      * Represent an Observer registered to a given Observable object.
      */
      */
     var Observer = /** @class */ (function () {
     var Observer = /** @class */ (function () {
-        function Observer(callback, mask, scope) {
+        /**
+         * Creates a new observer
+         * @param callback defines the callback to call when the observer is notified
+         * @param mask defines the mask of the observer (used to filter notifications)
+         * @param scope defines the current scope used to restore the JS context
+         */
+        function Observer(
+            /**
+             * Defines the callback to call when the observer is notified
+             */
+            callback, 
+            /**
+             * Defines the mask of the observer (used to filter notifications)
+             */
+            mask, 
+            /**
+             * Defines the current scope used to restore the JS context
+             */
+            scope) {
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
             this.callback = callback;
             this.callback = callback;
             this.mask = mask;
             this.mask = mask;
             this.scope = scope;
             this.scope = scope;
+            /**
+             * Gets or sets a property defining that the observer as to be unregistered after the next notification
+             */
+            this.unregisterOnNextCall = false;
         }
         }
         return Observer;
         return Observer;
     }());
     }());
@@ -6586,6 +6620,9 @@ var BABYLON;
     var MultiObserver = /** @class */ (function () {
     var MultiObserver = /** @class */ (function () {
         function MultiObserver() {
         function MultiObserver() {
         }
         }
+        /**
+         * Release associated resources
+         */
         MultiObserver.prototype.dispose = function () {
         MultiObserver.prototype.dispose = function () {
             if (this._observers && this._observables) {
             if (this._observers && this._observables) {
                 for (var index = 0; index < this._observers.length; index++) {
                 for (var index = 0; index < this._observers.length; index++) {
@@ -6595,6 +6632,14 @@ var BABYLON;
             this._observers = null;
             this._observers = null;
             this._observables = null;
             this._observables = null;
         };
         };
+        /**
+         * Raise a callback when one of the observable will notify
+         * @param observables defines a list of observables to watch
+         * @param callback defines the callback to call on notification
+         * @param mask defines the mask used to filter notifications
+         * @param scope defines the current scope used to restore the JS context
+         * @returns the new MultiObserver
+         */
         MultiObserver.Watch = function (observables, callback, mask, scope) {
         MultiObserver.Watch = function (observables, callback, mask, scope) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
@@ -6621,6 +6666,10 @@ var BABYLON;
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
      */
     var Observable = /** @class */ (function () {
     var Observable = /** @class */ (function () {
+        /**
+         * Creates a new observable
+         * @param onObserverAdded defines a callback to call when a new observer is added
+         */
         function Observable(onObserverAdded) {
         function Observable(onObserverAdded) {
             this._observers = new Array();
             this._observers = new Array();
             this._eventState = new EventState(0);
             this._eventState = new EventState(0);
@@ -6634,15 +6683,19 @@ var BABYLON;
          * @param mask the mask used to filter observers
          * @param mask the mask used to filter observers
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param scope optional scope for the callback to be called from
          * @param scope optional scope for the callback to be called from
+         * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
+         * @returns the new observer created for the callback
          */
          */
-        Observable.prototype.add = function (callback, mask, insertFirst, scope) {
+        Observable.prototype.add = function (callback, mask, insertFirst, scope, unregisterOnFirstCall) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
+            if (unregisterOnFirstCall === void 0) { unregisterOnFirstCall = false; }
             if (!callback) {
             if (!callback) {
                 return null;
                 return null;
             }
             }
             var observer = new Observer(callback, mask, scope);
             var observer = new Observer(callback, mask, scope);
+            observer.unregisterOnNextCall = unregisterOnFirstCall;
             if (insertFirst) {
             if (insertFirst) {
                 this._observers.unshift(observer);
                 this._observers.unshift(observer);
             }
             }
@@ -6656,7 +6709,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove an Observer from the Observable object
          * Remove an Observer from the Observable object
-         * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
+         * @param observer the instance of the Observer to remove
+         * @returns false if it doesn't belong to this Observable
          */
          */
         Observable.prototype.remove = function (observer) {
         Observable.prototype.remove = function (observer) {
             if (!observer) {
             if (!observer) {
@@ -6671,8 +6725,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove a callback from the Observable object
          * Remove a callback from the Observable object
-         * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
-         * @param scope optional scope. If used only the callbacks with this scope will be removed.
+         * @param callback the callback to remove
+         * @param scope optional scope. If used only the callbacks with this scope will be removed
+         * @returns false if it doesn't belong to this Observable
         */
         */
         Observable.prototype.removeCallback = function (callback, scope) {
         Observable.prototype.removeCallback = function (callback, scope) {
             for (var index = 0; index < this._observers.length; index++) {
             for (var index = 0; index < this._observers.length; index++) {
@@ -6683,11 +6738,20 @@ var BABYLON;
             }
             }
             return false;
             return false;
         };
         };
+        Observable.prototype._deferUnregister = function (observer) {
+            var _this = this;
+            BABYLON.Tools.SetImmediate(function () {
+                _this.remove(observer);
+            });
+        };
         /**
         /**
          * Notify all Observers by calling their respective callback with the given data
          * Notify all Observers by calling their respective callback with the given data
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
-         * @param eventData
-         * @param mask
+         * @param eventData defines the data to send to all observers
+         * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
          */
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6709,6 +6773,9 @@ var BABYLON;
                     else {
                     else {
                         state.lastReturnValue = obs.callback(eventData, state);
                         state.lastReturnValue = obs.callback(eventData, state);
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }
                 }
                 }
                 if (state.skipNextObservers) {
                 if (state.skipNextObservers) {
                     return false;
                     return false;
@@ -6725,11 +6792,12 @@ var BABYLON;
          *
          *
          * @param eventData The data to be sent to each callback
          * @param eventData The data to be sent to each callback
          * @param mask is used to filter observers defaults to -1
          * @param mask is used to filter observers defaults to -1
-         * @param target the callback target (see EventState)
-         * @param currentTarget The current object in the bubbling phase
+         * @param target defines the callback target (see EventState)
+         * @param currentTarget defines he current object in the bubbling phase
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          */
          */
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
+            var _this = this;
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             // create an empty promise
             // create an empty promise
             var p = Promise.resolve(eventData);
             var p = Promise.resolve(eventData);
@@ -6760,6 +6828,9 @@ var BABYLON;
                             return obs.callback(eventData, state);
                             return obs.callback(eventData, state);
                         });
                         });
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        _this._deferUnregister(obs);
+                    }
                 }
                 }
             });
             });
             // return the eventData
             // return the eventData
@@ -6767,8 +6838,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Notify a specific observer
          * Notify a specific observer
-         * @param eventData
-         * @param mask
+         * @param observer defines the observer to notify
+         * @param eventData defines the data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
          */
          */
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6778,7 +6850,8 @@ var BABYLON;
             observer.callback(eventData, state);
             observer.callback(eventData, state);
         };
         };
         /**
         /**
-         * return true is the Observable has at least one Observer registered
+         * Gets a boolean indicating if the observable has at least one observer
+         * @returns true is the Observable has at least one Observer registered
          */
          */
         Observable.prototype.hasObservers = function () {
         Observable.prototype.hasObservers = function () {
             return this._observers.length > 0;
             return this._observers.length > 0;
@@ -6791,8 +6864,9 @@ var BABYLON;
             this._onObserverAdded = null;
             this._onObserverAdded = null;
         };
         };
         /**
         /**
-        * Clone the current observable
-        */
+         * Clone the current observable
+         * @returns a new observable
+         */
         Observable.prototype.clone = function () {
         Observable.prototype.clone = function () {
             var result = new Observable();
             var result = new Observable();
             result._observers = this._observers.slice(0);
             result._observers = this._observers.slice(0);
@@ -6800,8 +6874,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Does this observable handles observer registered with a given mask
          * Does this observable handles observer registered with a given mask
-         * @param {number} trigger - the mask to be tested
-         * @return {boolean} whether or not one observer registered with the given mask is handeled
+         * @param mask defines the mask to be tested
+         * @return whether or not one observer registered with the given mask is handeled
         **/
         **/
         Observable.prototype.hasSpecificMask = function (mask) {
         Observable.prototype.hasSpecificMask = function (mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -15181,19 +15255,31 @@ var BABYLON;
             configurable: true
             configurable: true
         });
         });
         /**
         /**
-         * Sets a new pivot matrix to the mesh.
-         * Returns the AbstractMesh.
+         * Sets a new matrix to apply before all other transformation
+         * @param matrix defines the transform matrix
+         * @returns the current TransformNode
+         */
+        TransformNode.prototype.setPreTransformMatrix = function (matrix) {
+            return this.setPivotMatrix(matrix, false);
+        };
+        /**
+         * Sets a new pivot matrix to the current node
+         * @param matrix defines the new pivot matrix to use
+         * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
+         * @returns the current TransformNode
         */
         */
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
-            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = false; }
+            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = true; }
             this._pivotMatrix = matrix.clone();
             this._pivotMatrix = matrix.clone();
             this._cache.pivotMatrixUpdated = true;
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+            if (this._postMultiplyPivotMatrix) {
+                if (!this._pivotMatrixInverse) {
+                    this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
+                }
+                else {
+                    this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+                }
             }
             }
             return this;
             return this;
         };
         };
@@ -15357,6 +15443,12 @@ var BABYLON;
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             return this;
             return this;
         };
         };
+        /**
+         * Sets a new pivot point to the current node
+         * @param point defines the new pivot point to use
+         * @param space defines if the point is in world or local space (local by default)
+         * @returns the current TransformNode
+        */
         TransformNode.prototype.setPivotPoint = function (point, space) {
         TransformNode.prototype.setPivotPoint = function (point, space) {
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (this.getScene().getRenderId() == 0) {
             if (this.getScene().getRenderId() == 0) {
@@ -15368,18 +15460,7 @@ var BABYLON;
                 wm.invertToRef(tmat);
                 wm.invertToRef(tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
             }
             }
-            BABYLON.Vector3.TransformCoordinatesToRef(point, wm, this.position);
-            this._pivotMatrix.m[12] = -point.x;
-            this._pivotMatrix.m[13] = -point.y;
-            this._pivotMatrix.m[14] = -point.z;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
-            }
-            this._cache.pivotMatrixUpdated = true;
-            return this;
+            return this.setPivotMatrix(BABYLON.Matrix.Translation(point.x, point.y, point.z), true);
         };
         };
         /**
         /**
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
@@ -15750,9 +15831,6 @@ var BABYLON;
             this._afterComputeWorldMatrix();
             this._afterComputeWorldMatrix();
             // Absolute position
             // Absolute position
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
-            if (this._pivotMatrixInverse) {
-                BABYLON.Vector3.TransformCoordinatesToRef(this._absolutePosition, this._pivotMatrixInverse, this._absolutePosition);
-            }
             // Callbacks
             // Callbacks
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             if (!this._poseMatrix) {
             if (!this._poseMatrix) {
@@ -15839,7 +15917,7 @@ var BABYLON;
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
             }
             }
             if (parsedTransformNode.localMatrix) {
             if (parsedTransformNode.localMatrix) {
-                transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
+                transformNode.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
             }
             }
             else if (parsedTransformNode.pivotMatrix) {
             else if (parsedTransformNode.pivotMatrix) {
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
@@ -27952,7 +28030,7 @@ var BABYLON;
             }
             }
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             if (parsedMesh.localMatrix) {
             if (parsedMesh.localMatrix) {
-                mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
+                mesh.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
             }
             }
             else if (parsedMesh.pivotMatrix) {
             else if (parsedMesh.pivotMatrix) {
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
@@ -73549,7 +73627,7 @@ var BABYLON;
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
-                mesh.setPivotMatrix(p);
+                mesh.setPreTransformMatrix(p);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
                 //calculate the translation
                 //calculate the translation
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
@@ -73559,7 +73637,7 @@ var BABYLON;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 //rotation is back
                 //rotation is back
                 mesh.rotationQuaternion = rotationQuaternion;
                 mesh.rotationQuaternion = rotationQuaternion;
-                mesh.setPivotMatrix(oldPivot);
+                mesh.setPreTransformMatrix(oldPivot);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
             }
             }
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {

+ 123 - 45
dist/preview release/es6.js

@@ -6550,12 +6550,24 @@ var BABYLON;
      */
      */
     var EventState = /** @class */ (function () {
     var EventState = /** @class */ (function () {
         /**
         /**
-        * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
-        */
+         * Create a new EventState
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         */
         function EventState(mask, skipNextObservers, target, currentTarget) {
         function EventState(mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.initalize(mask, skipNextObservers, target, currentTarget);
             this.initalize(mask, skipNextObservers, target, currentTarget);
         }
         }
+        /**
+         * Initialize the current event state
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns the current event state
+         */
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
         EventState.prototype.initalize = function (mask, skipNextObservers, target, currentTarget) {
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             if (skipNextObservers === void 0) { skipNextObservers = false; }
             this.mask = mask;
             this.mask = mask;
@@ -6571,11 +6583,33 @@ var BABYLON;
      * Represent an Observer registered to a given Observable object.
      * Represent an Observer registered to a given Observable object.
      */
      */
     var Observer = /** @class */ (function () {
     var Observer = /** @class */ (function () {
-        function Observer(callback, mask, scope) {
+        /**
+         * Creates a new observer
+         * @param callback defines the callback to call when the observer is notified
+         * @param mask defines the mask of the observer (used to filter notifications)
+         * @param scope defines the current scope used to restore the JS context
+         */
+        function Observer(
+            /**
+             * Defines the callback to call when the observer is notified
+             */
+            callback, 
+            /**
+             * Defines the mask of the observer (used to filter notifications)
+             */
+            mask, 
+            /**
+             * Defines the current scope used to restore the JS context
+             */
+            scope) {
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
             this.callback = callback;
             this.callback = callback;
             this.mask = mask;
             this.mask = mask;
             this.scope = scope;
             this.scope = scope;
+            /**
+             * Gets or sets a property defining that the observer as to be unregistered after the next notification
+             */
+            this.unregisterOnNextCall = false;
         }
         }
         return Observer;
         return Observer;
     }());
     }());
@@ -6586,6 +6620,9 @@ var BABYLON;
     var MultiObserver = /** @class */ (function () {
     var MultiObserver = /** @class */ (function () {
         function MultiObserver() {
         function MultiObserver() {
         }
         }
+        /**
+         * Release associated resources
+         */
         MultiObserver.prototype.dispose = function () {
         MultiObserver.prototype.dispose = function () {
             if (this._observers && this._observables) {
             if (this._observers && this._observables) {
                 for (var index = 0; index < this._observers.length; index++) {
                 for (var index = 0; index < this._observers.length; index++) {
@@ -6595,6 +6632,14 @@ var BABYLON;
             this._observers = null;
             this._observers = null;
             this._observables = null;
             this._observables = null;
         };
         };
+        /**
+         * Raise a callback when one of the observable will notify
+         * @param observables defines a list of observables to watch
+         * @param callback defines the callback to call on notification
+         * @param mask defines the mask used to filter notifications
+         * @param scope defines the current scope used to restore the JS context
+         * @returns the new MultiObserver
+         */
         MultiObserver.Watch = function (observables, callback, mask, scope) {
         MultiObserver.Watch = function (observables, callback, mask, scope) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
@@ -6621,6 +6666,10 @@ var BABYLON;
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
      */
     var Observable = /** @class */ (function () {
     var Observable = /** @class */ (function () {
+        /**
+         * Creates a new observable
+         * @param onObserverAdded defines a callback to call when a new observer is added
+         */
         function Observable(onObserverAdded) {
         function Observable(onObserverAdded) {
             this._observers = new Array();
             this._observers = new Array();
             this._eventState = new EventState(0);
             this._eventState = new EventState(0);
@@ -6634,15 +6683,19 @@ var BABYLON;
          * @param mask the mask used to filter observers
          * @param mask the mask used to filter observers
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param scope optional scope for the callback to be called from
          * @param scope optional scope for the callback to be called from
+         * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
+         * @returns the new observer created for the callback
          */
          */
-        Observable.prototype.add = function (callback, mask, insertFirst, scope) {
+        Observable.prototype.add = function (callback, mask, insertFirst, scope, unregisterOnFirstCall) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (insertFirst === void 0) { insertFirst = false; }
             if (scope === void 0) { scope = null; }
             if (scope === void 0) { scope = null; }
+            if (unregisterOnFirstCall === void 0) { unregisterOnFirstCall = false; }
             if (!callback) {
             if (!callback) {
                 return null;
                 return null;
             }
             }
             var observer = new Observer(callback, mask, scope);
             var observer = new Observer(callback, mask, scope);
+            observer.unregisterOnNextCall = unregisterOnFirstCall;
             if (insertFirst) {
             if (insertFirst) {
                 this._observers.unshift(observer);
                 this._observers.unshift(observer);
             }
             }
@@ -6656,7 +6709,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove an Observer from the Observable object
          * Remove an Observer from the Observable object
-         * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
+         * @param observer the instance of the Observer to remove
+         * @returns false if it doesn't belong to this Observable
          */
          */
         Observable.prototype.remove = function (observer) {
         Observable.prototype.remove = function (observer) {
             if (!observer) {
             if (!observer) {
@@ -6671,8 +6725,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Remove a callback from the Observable object
          * Remove a callback from the Observable object
-         * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
-         * @param scope optional scope. If used only the callbacks with this scope will be removed.
+         * @param callback the callback to remove
+         * @param scope optional scope. If used only the callbacks with this scope will be removed
+         * @returns false if it doesn't belong to this Observable
         */
         */
         Observable.prototype.removeCallback = function (callback, scope) {
         Observable.prototype.removeCallback = function (callback, scope) {
             for (var index = 0; index < this._observers.length; index++) {
             for (var index = 0; index < this._observers.length; index++) {
@@ -6683,11 +6738,20 @@ var BABYLON;
             }
             }
             return false;
             return false;
         };
         };
+        Observable.prototype._deferUnregister = function (observer) {
+            var _this = this;
+            BABYLON.Tools.SetImmediate(function () {
+                _this.remove(observer);
+            });
+        };
         /**
         /**
          * Notify all Observers by calling their respective callback with the given data
          * Notify all Observers by calling their respective callback with the given data
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
-         * @param eventData
-         * @param mask
+         * @param eventData defines the data to send to all observers
+         * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
          */
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6709,6 +6773,9 @@ var BABYLON;
                     else {
                     else {
                         state.lastReturnValue = obs.callback(eventData, state);
                         state.lastReturnValue = obs.callback(eventData, state);
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }
                 }
                 }
                 if (state.skipNextObservers) {
                 if (state.skipNextObservers) {
                     return false;
                     return false;
@@ -6725,11 +6792,12 @@ var BABYLON;
          *
          *
          * @param eventData The data to be sent to each callback
          * @param eventData The data to be sent to each callback
          * @param mask is used to filter observers defaults to -1
          * @param mask is used to filter observers defaults to -1
-         * @param target the callback target (see EventState)
-         * @param currentTarget The current object in the bubbling phase
+         * @param target defines the callback target (see EventState)
+         * @param currentTarget defines he current object in the bubbling phase
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          */
          */
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
         Observable.prototype.notifyObserversWithPromise = function (eventData, mask, target, currentTarget) {
+            var _this = this;
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
             // create an empty promise
             // create an empty promise
             var p = Promise.resolve(eventData);
             var p = Promise.resolve(eventData);
@@ -6760,6 +6828,9 @@ var BABYLON;
                             return obs.callback(eventData, state);
                             return obs.callback(eventData, state);
                         });
                         });
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        _this._deferUnregister(obs);
+                    }
                 }
                 }
             });
             });
             // return the eventData
             // return the eventData
@@ -6767,8 +6838,9 @@ var BABYLON;
         };
         };
         /**
         /**
          * Notify a specific observer
          * Notify a specific observer
-         * @param eventData
-         * @param mask
+         * @param observer defines the observer to notify
+         * @param eventData defines the data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
          */
          */
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
         Observable.prototype.notifyObserver = function (observer, eventData, mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -6778,7 +6850,8 @@ var BABYLON;
             observer.callback(eventData, state);
             observer.callback(eventData, state);
         };
         };
         /**
         /**
-         * return true is the Observable has at least one Observer registered
+         * Gets a boolean indicating if the observable has at least one observer
+         * @returns true is the Observable has at least one Observer registered
          */
          */
         Observable.prototype.hasObservers = function () {
         Observable.prototype.hasObservers = function () {
             return this._observers.length > 0;
             return this._observers.length > 0;
@@ -6791,8 +6864,9 @@ var BABYLON;
             this._onObserverAdded = null;
             this._onObserverAdded = null;
         };
         };
         /**
         /**
-        * Clone the current observable
-        */
+         * Clone the current observable
+         * @returns a new observable
+         */
         Observable.prototype.clone = function () {
         Observable.prototype.clone = function () {
             var result = new Observable();
             var result = new Observable();
             result._observers = this._observers.slice(0);
             result._observers = this._observers.slice(0);
@@ -6800,8 +6874,8 @@ var BABYLON;
         };
         };
         /**
         /**
          * Does this observable handles observer registered with a given mask
          * Does this observable handles observer registered with a given mask
-         * @param {number} trigger - the mask to be tested
-         * @return {boolean} whether or not one observer registered with the given mask is handeled
+         * @param mask defines the mask to be tested
+         * @return whether or not one observer registered with the given mask is handeled
         **/
         **/
         Observable.prototype.hasSpecificMask = function (mask) {
         Observable.prototype.hasSpecificMask = function (mask) {
             if (mask === void 0) { mask = -1; }
             if (mask === void 0) { mask = -1; }
@@ -15181,19 +15255,31 @@ var BABYLON;
             configurable: true
             configurable: true
         });
         });
         /**
         /**
-         * Sets a new pivot matrix to the mesh.
-         * Returns the AbstractMesh.
+         * Sets a new matrix to apply before all other transformation
+         * @param matrix defines the transform matrix
+         * @returns the current TransformNode
+         */
+        TransformNode.prototype.setPreTransformMatrix = function (matrix) {
+            return this.setPivotMatrix(matrix, false);
+        };
+        /**
+         * Sets a new pivot matrix to the current node
+         * @param matrix defines the new pivot matrix to use
+         * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
+         * @returns the current TransformNode
         */
         */
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
         TransformNode.prototype.setPivotMatrix = function (matrix, postMultiplyPivotMatrix) {
-            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = false; }
+            if (postMultiplyPivotMatrix === void 0) { postMultiplyPivotMatrix = true; }
             this._pivotMatrix = matrix.clone();
             this._pivotMatrix = matrix.clone();
             this._cache.pivotMatrixUpdated = true;
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+            if (this._postMultiplyPivotMatrix) {
+                if (!this._pivotMatrixInverse) {
+                    this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
+                }
+                else {
+                    this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+                }
             }
             }
             return this;
             return this;
         };
         };
@@ -15357,6 +15443,12 @@ var BABYLON;
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             BABYLON.Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
             return this;
             return this;
         };
         };
+        /**
+         * Sets a new pivot point to the current node
+         * @param point defines the new pivot point to use
+         * @param space defines if the point is in world or local space (local by default)
+         * @returns the current TransformNode
+        */
         TransformNode.prototype.setPivotPoint = function (point, space) {
         TransformNode.prototype.setPivotPoint = function (point, space) {
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (space === void 0) { space = BABYLON.Space.LOCAL; }
             if (this.getScene().getRenderId() == 0) {
             if (this.getScene().getRenderId() == 0) {
@@ -15368,18 +15460,7 @@ var BABYLON;
                 wm.invertToRef(tmat);
                 wm.invertToRef(tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
                 point = BABYLON.Vector3.TransformCoordinates(point, tmat);
             }
             }
-            BABYLON.Vector3.TransformCoordinatesToRef(point, wm, this.position);
-            this._pivotMatrix.m[12] = -point.x;
-            this._pivotMatrix.m[13] = -point.y;
-            this._pivotMatrix.m[14] = -point.z;
-            if (!this._pivotMatrixInverse) {
-                this._pivotMatrixInverse = BABYLON.Matrix.Invert(this._pivotMatrix);
-            }
-            else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
-            }
-            this._cache.pivotMatrixUpdated = true;
-            return this;
+            return this.setPivotMatrix(BABYLON.Matrix.Translation(point.x, point.y, point.z), true);
         };
         };
         /**
         /**
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
          * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
@@ -15750,9 +15831,6 @@ var BABYLON;
             this._afterComputeWorldMatrix();
             this._afterComputeWorldMatrix();
             // Absolute position
             // Absolute position
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
-            if (this._pivotMatrixInverse) {
-                BABYLON.Vector3.TransformCoordinatesToRef(this._absolutePosition, this._pivotMatrixInverse, this._absolutePosition);
-            }
             // Callbacks
             // Callbacks
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             if (!this._poseMatrix) {
             if (!this._poseMatrix) {
@@ -15839,7 +15917,7 @@ var BABYLON;
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
                 BABYLON.Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
             }
             }
             if (parsedTransformNode.localMatrix) {
             if (parsedTransformNode.localMatrix) {
-                transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
+                transformNode.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.localMatrix));
             }
             }
             else if (parsedTransformNode.pivotMatrix) {
             else if (parsedTransformNode.pivotMatrix) {
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
                 transformNode.setPivotMatrix(BABYLON.Matrix.FromArray(parsedTransformNode.pivotMatrix));
@@ -27952,7 +28030,7 @@ var BABYLON;
             }
             }
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             mesh.scaling = BABYLON.Vector3.FromArray(parsedMesh.scaling);
             if (parsedMesh.localMatrix) {
             if (parsedMesh.localMatrix) {
-                mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
+                mesh.setPreTransformMatrix(BABYLON.Matrix.FromArray(parsedMesh.localMatrix));
             }
             }
             else if (parsedMesh.pivotMatrix) {
             else if (parsedMesh.pivotMatrix) {
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
                 mesh.setPivotMatrix(BABYLON.Matrix.FromArray(parsedMesh.pivotMatrix));
@@ -73807,7 +73885,7 @@ var BABYLON;
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 var oldPivot = mesh.getPivotMatrix() || BABYLON.Matrix.Translation(0, 0, 0);
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
                 var p = BABYLON.Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
-                mesh.setPivotMatrix(p);
+                mesh.setPreTransformMatrix(p);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
                 //calculate the translation
                 //calculate the translation
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
                 var translation = boundingInfo.boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
@@ -73817,7 +73895,7 @@ var BABYLON;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 this._tmpDeltaPosition.y += boundingInfo.boundingBox.extendSizeWorld.y;
                 //rotation is back
                 //rotation is back
                 mesh.rotationQuaternion = rotationQuaternion;
                 mesh.rotationQuaternion = rotationQuaternion;
-                mesh.setPivotMatrix(oldPivot);
+                mesh.setPreTransformMatrix(oldPivot);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
             }
             }
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {
             else if (impostor.type === BABYLON.PhysicsImpostor.MeshImpostor) {

+ 2 - 241
dist/preview release/typedocValidationBaseline.json

@@ -1,7 +1,7 @@
 {
 {
-  "errors": 8172,
+  "errors": 8133,
   "babylon.typedoc.json": {
   "babylon.typedoc.json": {
-    "errors": 8172,
+    "errors": 8133,
     "AnimationKeyInterpolation": {
     "AnimationKeyInterpolation": {
       "Enumeration": {
       "Enumeration": {
         "Comments": {
         "Comments": {
@@ -14203,37 +14203,6 @@
         }
         }
       }
       }
     },
     },
-    "EventState": {
-      "Method": {
-        "initalize": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "mask": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "skipNextObservers": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "target": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "currentTarget": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      }
-    },
     "ExecuteCodeAction": {
     "ExecuteCodeAction": {
       "Class": {
       "Class": {
         "Comments": {
         "Comments": {
@@ -21963,42 +21932,6 @@
         }
         }
       }
       }
     },
     },
-    "MultiObserver": {
-      "Method": {
-        "dispose": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "Watch": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "observables": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "callback": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "mask": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "scope": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      }
-    },
     "MultiRenderTarget": {
     "MultiRenderTarget": {
       "Class": {
       "Class": {
         "Comments": {
         "Comments": {
@@ -22481,144 +22414,6 @@
         }
         }
       }
       }
     },
     },
-    "Observable": {
-      "Constructor": {
-        "new Observable": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "onObserverAdded": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      },
-      "Property": {
-        "_observers": {
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      },
-      "Method": {
-        "add": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
-        "clone": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
-        "hasObservers": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
-        "hasSpecificMask": {
-          "Parameter": {
-            "mask": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "notifyObserver": {
-          "Parameter": {
-            "observer": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "eventData": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "notifyObservers": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "eventData": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "target": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "currentTarget": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "remove": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
-        "removeCallback": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        }
-      }
-    },
-    "Observer": {
-      "Constructor": {
-        "new Observer": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "callback": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "mask": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "scope": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      },
-      "Property": {
-        "callback": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "mask": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "scope": {
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      }
-    },
     "Octree": {
     "Octree": {
       "Class": {
       "Class": {
         "Comments": {
         "Comments": {
@@ -37989,40 +37784,6 @@
             }
             }
           }
           }
         },
         },
-        "setPivotMatrix": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "matrix": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "postMultiplyPivotMatrix": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "setPivotPoint": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "point": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "space": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
         "setPositionWithLocalVector": {
         "setPositionWithLocalVector": {
           "Comments": {
           "Comments": {
             "MissingReturn": true
             "MissingReturn": true

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 30
dist/preview release/viewer/babylon.viewer.js


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

@@ -25,7 +25,7 @@
    ([carloslanderas](https://github.com/carloslanderas))
    ([carloslanderas](https://github.com/carloslanderas))
 - VRHelper now exposes onNewMeshPicked observable that will notify a PickingInfo object after meshSelectionPredicate evaluation
 - VRHelper now exposes onNewMeshPicked observable that will notify a PickingInfo object after meshSelectionPredicate evaluation
    ([carloslanderas](https://github.com/carloslanderas))
    ([carloslanderas](https://github.com/carloslanderas))
-- `AssetsManager` will now clear its `tasks` lsit from all successfully loaded tasks ([deltakosh](https://github.com/deltakosh))tasks ([deltakosh](https://github.com/deltakosh))
+- `AssetsManager` will now clear its `tasks` lsit from all successfully loaded tasks ([deltakosh](https://github.com/deltakosh))
 - Added documentation to WebVRCamera and VRExperienceHelper ([trevordev](https://github.com/trevordev))
 - Added documentation to WebVRCamera and VRExperienceHelper ([trevordev](https://github.com/trevordev))
 - Introduced `isStroke` on `HighlightLayerOptions` which makes the highlight solid ([PixelsCommander](https://github.com/pixelscommander))
 - Introduced `isStroke` on `HighlightLayerOptions` which makes the highlight solid ([PixelsCommander](https://github.com/pixelscommander))
 - (Viewer) There is now an option to paste payload instead of a URL for configuration ([RaananW](https://github.com/RaananW))
 - (Viewer) There is now an option to paste payload instead of a URL for configuration ([RaananW](https://github.com/RaananW))
@@ -46,8 +46,10 @@
 - (Viewer) Introducing the viewer labs - testing new features. ([RaananW](https://github.com/RaananW))
 - (Viewer) Introducing the viewer labs - testing new features. ([RaananW](https://github.com/RaananW))
 - AssetContainer Class and loading methods. ([trevordev](https://github.com/trevordev))
 - AssetContainer Class and loading methods. ([trevordev](https://github.com/trevordev))
 - KeepAssets class and AssetContainer.moveAllFromScene ([HoloLite](http://www.html5gamedevs.com/profile/28694-hololite/), [trevordev](https://github.com/trevordev))
 - KeepAssets class and AssetContainer.moveAllFromScene ([HoloLite](http://www.html5gamedevs.com/profile/28694-hololite/), [trevordev](https://github.com/trevordev))
+- (Viewer) It is now possible to update parts of the configuration without rcreating the objects. ([RaananW](https://github.com/RaananW))
 
 
 ## Bug fixes
 ## Bug fixes
+- `setPivotMatrix` ws not setting pivot correctly. This is now fixed. We also introduced a new `setPreTransformMatrix` to reproduce the sometimes needed behavior of the previous `setPivotMatrix` function ([deltakosh](https://github.com/deltakosh))
 - Texture extension detection in `Engine.CreateTexture` ([sebavan](https://github.com/sebavan))
 - Texture extension detection in `Engine.CreateTexture` ([sebavan](https://github.com/sebavan))
 - Fixed a bug with merging vertex data ([bghgary](https://github.com/bghgary))
 - Fixed a bug with merging vertex data ([bghgary](https://github.com/bghgary))
 
 

+ 1 - 1
src/Mesh/babylon.mesh.ts

@@ -2382,7 +2382,7 @@
             mesh.scaling = Vector3.FromArray(parsedMesh.scaling);
             mesh.scaling = Vector3.FromArray(parsedMesh.scaling);
 
 
             if (parsedMesh.localMatrix) {
             if (parsedMesh.localMatrix) {
-                mesh.setPivotMatrix(Matrix.FromArray(parsedMesh.localMatrix));
+                mesh.setPreTransformMatrix(Matrix.FromArray(parsedMesh.localMatrix));
             } else if (parsedMesh.pivotMatrix) {
             } else if (parsedMesh.pivotMatrix) {
                 mesh.setPivotMatrix(Matrix.FromArray(parsedMesh.pivotMatrix));
                 mesh.setPivotMatrix(Matrix.FromArray(parsedMesh.pivotMatrix));
             }
             }

+ 28 - 25
src/Mesh/babylon.transformNode.ts

@@ -208,18 +208,31 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
-         * Sets a new pivot matrix to the mesh.  
-         * Returns the AbstractMesh.
+         * Sets a new matrix to apply before all other transformation
+         * @param matrix defines the transform matrix
+         * @returns the current TransformNode
+         */
+        public setPreTransformMatrix(matrix: Matrix): TransformNode {
+            return this.setPivotMatrix(matrix, false);
+        }
+
+        /**
+         * Sets a new pivot matrix to the current node
+         * @param matrix defines the new pivot matrix to use
+         * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
+         * @returns the current TransformNode
         */
         */
-        public setPivotMatrix(matrix: Matrix, postMultiplyPivotMatrix = false): TransformNode {
+        public setPivotMatrix(matrix: Matrix, postMultiplyPivotMatrix = true): TransformNode {
             this._pivotMatrix = matrix.clone();
             this._pivotMatrix = matrix.clone();
             this._cache.pivotMatrixUpdated = true;
             this._cache.pivotMatrixUpdated = true;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
             this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
 
 
-            if(!this._pivotMatrixInverse){
-                this._pivotMatrixInverse = Matrix.Invert(this._pivotMatrix);
-            } else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+            if (this._postMultiplyPivotMatrix) {
+                if (!this._pivotMatrixInverse) {
+                    this._pivotMatrixInverse = Matrix.Invert(this._pivotMatrix);
+                } else {
+                    this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
+                }
             }
             }
 
 
             return this;
             return this;
@@ -393,6 +406,12 @@ module BABYLON {
             return this;
             return this;
         }
         }
 
 
+        /**
+         * Sets a new pivot point to the current node
+         * @param point defines the new pivot point to use
+         * @param space defines if the point is in world or local space (local by default)
+         * @returns the current TransformNode
+        */        
         public setPivotPoint(point: Vector3, space: Space = Space.LOCAL): TransformNode {
         public setPivotPoint(point: Vector3, space: Space = Space.LOCAL): TransformNode {
             if (this.getScene().getRenderId() == 0) {
             if (this.getScene().getRenderId() == 0) {
                 this.computeWorldMatrix(true);
                 this.computeWorldMatrix(true);
@@ -406,19 +425,7 @@ module BABYLON {
                 point = Vector3.TransformCoordinates(point, tmat);
                 point = Vector3.TransformCoordinates(point, tmat);
             }
             }
 
 
-            Vector3.TransformCoordinatesToRef(point, wm, this.position);
-            this._pivotMatrix.m[12] = -point.x;
-            this._pivotMatrix.m[13] = -point.y;
-            this._pivotMatrix.m[14] = -point.z;
-
-            if(!this._pivotMatrixInverse){
-                this._pivotMatrixInverse = Matrix.Invert(this._pivotMatrix);
-            } else {
-                this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
-            }
-            
-            this._cache.pivotMatrixUpdated = true;
-            return this;
+            return this.setPivotMatrix(Matrix.Translation(point.x, point.y, point.z), true);
         }
         }
 
 
         /**
         /**
@@ -835,10 +842,6 @@ module BABYLON {
             // Absolute position
             // Absolute position
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
             this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
 
 
-            if(this._pivotMatrixInverse){
-                Vector3.TransformCoordinatesToRef(this._absolutePosition, this._pivotMatrixInverse, this._absolutePosition);
-            }
-
             // Callbacks
             // Callbacks
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
             this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
 
 
@@ -946,7 +949,7 @@ module BABYLON {
             }
             }
 
 
             if (parsedTransformNode.localMatrix) {
             if (parsedTransformNode.localMatrix) {
-                transformNode.setPivotMatrix(Matrix.FromArray(parsedTransformNode.localMatrix));
+                transformNode.setPreTransformMatrix(Matrix.FromArray(parsedTransformNode.localMatrix));
             } else if (parsedTransformNode.pivotMatrix) {
             } else if (parsedTransformNode.pivotMatrix) {
                 transformNode.setPivotMatrix(Matrix.FromArray(parsedTransformNode.pivotMatrix));
                 transformNode.setPivotMatrix(Matrix.FromArray(parsedTransformNode.pivotMatrix));
             }
             }

+ 2 - 2
src/Physics/Plugins/babylon.cannonJSPlugin.ts

@@ -435,7 +435,7 @@
 
 
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
                 var p = Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);
-                mesh.setPivotMatrix(p);
+                mesh.setPreTransformMatrix(p);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
 
 
                 //calculate the translation
                 //calculate the translation
@@ -448,7 +448,7 @@
                 //rotation is back
                 //rotation is back
                 mesh.rotationQuaternion = rotationQuaternion;
                 mesh.rotationQuaternion = rotationQuaternion;
 
 
-                mesh.setPivotMatrix(oldPivot);
+                mesh.setPreTransformMatrix(oldPivot);
                 mesh.computeWorldMatrix(true);
                 mesh.computeWorldMatrix(true);
             } else if (impostor.type === PhysicsImpostor.MeshImpostor) {
             } else if (impostor.type === PhysicsImpostor.MeshImpostor) {
                 this._tmpDeltaPosition.copyFromFloats(0, 0, 0);
                 this._tmpDeltaPosition.copyFromFloats(0, 0, 0);

+ 93 - 19
src/Tools/babylon.observable.ts

@@ -6,12 +6,24 @@
     export class EventState {
     export class EventState {
 
 
         /**
         /**
-        * If the callback of a given Observer set skipNextObservers to true the following observers will be ignored
-        */
+         * Create a new EventState
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         */
         constructor(mask: number, skipNextObservers = false, target?: any, currentTarget?: any) {
         constructor(mask: number, skipNextObservers = false, target?: any, currentTarget?: any) {
             this.initalize(mask, skipNextObservers, target, currentTarget);
             this.initalize(mask, skipNextObservers, target, currentTarget);
         }
         }
 
 
+        /**
+         * Initialize the current event state
+         * @param mask defines the mask associated with this state
+         * @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns the current event state
+         */
         public initalize(mask: number, skipNextObservers = false, target?: any, currentTarget?: any): EventState {
         public initalize(mask: number, skipNextObservers = false, target?: any, currentTarget?: any): EventState {
             this.mask = mask;
             this.mask = mask;
             this.skipNextObservers = skipNextObservers;
             this.skipNextObservers = skipNextObservers;
@@ -51,7 +63,30 @@
      * Represent an Observer registered to a given Observable object.
      * Represent an Observer registered to a given Observable object.
      */
      */
     export class Observer<T> {
     export class Observer<T> {
-        constructor(public callback: (eventData: T, eventState: EventState) => void, public mask: number, public scope: any = null) {
+        /**
+         * Gets or sets a property defining that the observer as to be unregistered after the next notification
+         */
+        public unregisterOnNextCall = false;
+
+        /**
+         * Creates a new observer
+         * @param callback defines the callback to call when the observer is notified
+         * @param mask defines the mask of the observer (used to filter notifications)
+         * @param scope defines the current scope used to restore the JS context
+         */
+        constructor(
+            /**
+             * Defines the callback to call when the observer is notified
+             */
+            public callback: (eventData: T, eventState: EventState) => void, 
+            /**
+             * Defines the mask of the observer (used to filter notifications)
+             */
+            public mask: number, 
+            /**
+             * Defines the current scope used to restore the JS context
+             */
+            public scope: any = null) {
         }
         }
     }
     }
 
 
@@ -62,6 +97,9 @@
         private _observers: Nullable<Observer<T>[]>;
         private _observers: Nullable<Observer<T>[]>;
         private _observables: Nullable<Observable<T>[]>;
         private _observables: Nullable<Observable<T>[]>;
 
 
+        /**
+         * Release associated resources
+         */
         public dispose(): void {
         public dispose(): void {
             if (this._observers && this._observables) {
             if (this._observers && this._observables) {
                 for (var index = 0; index < this._observers.length; index++) {
                 for (var index = 0; index < this._observers.length; index++) {
@@ -73,6 +111,14 @@
             this._observables = null;
             this._observables = null;
         }
         }
 
 
+        /**
+         * Raise a callback when one of the observable will notify
+         * @param observables defines a list of observables to watch
+         * @param callback defines the callback to call on notification
+         * @param mask defines the mask used to filter notifications
+         * @param scope defines the current scope used to restore the JS context
+         * @returns the new MultiObserver
+         */
         public static Watch<T>(observables: Observable<T>[], callback: (eventData: T, eventState: EventState) => void, mask: number = -1, scope: any = null): MultiObserver<T> {
         public static Watch<T>(observables: Observable<T>[], callback: (eventData: T, eventState: EventState) => void, mask: number = -1, scope: any = null): MultiObserver<T> {
             let result = new MultiObserver<T>();
             let result = new MultiObserver<T>();
 
 
@@ -98,12 +144,16 @@
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      * A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
      */
      */
     export class Observable<T> {
     export class Observable<T> {
-        _observers = new Array<Observer<T>>();
+        private _observers = new Array<Observer<T>>();
 
 
         private _eventState: EventState;
         private _eventState: EventState;
 
 
         private _onObserverAdded: Nullable<(observer: Observer<T>) => void>;
         private _onObserverAdded: Nullable<(observer: Observer<T>) => void>;
 
 
+        /**
+         * Creates a new observable
+         * @param onObserverAdded defines a callback to call when a new observer is added
+         */
         constructor(onObserverAdded?: (observer: Observer<T>) => void) {
         constructor(onObserverAdded?: (observer: Observer<T>) => void) {
             this._eventState = new EventState(0);
             this._eventState = new EventState(0);
 
 
@@ -118,13 +168,16 @@
          * @param mask the mask used to filter observers
          * @param mask the mask used to filter observers
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param insertFirst if true the callback will be inserted at the first position, hence executed before the others ones. If false (default behavior) the callback will be inserted at the last position, executed after all the others already present.
          * @param scope optional scope for the callback to be called from
          * @param scope optional scope for the callback to be called from
+         * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
+         * @returns the new observer created for the callback
          */
          */
-        public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null): Nullable<Observer<T>> {
+        public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null, unregisterOnFirstCall = false): Nullable<Observer<T>> {
             if (!callback) {
             if (!callback) {
                 return null;
                 return null;
             }
             }
 
 
             var observer = new Observer(callback, mask, scope);
             var observer = new Observer(callback, mask, scope);
+            observer.unregisterOnNextCall = unregisterOnFirstCall;
 
 
             if (insertFirst) {
             if (insertFirst) {
                 this._observers.unshift(observer);
                 this._observers.unshift(observer);
@@ -141,7 +194,8 @@
 
 
         /**
         /**
          * Remove an Observer from the Observable object
          * Remove an Observer from the Observable object
-         * @param observer the instance of the Observer to remove. If it doesn't belong to this Observable, false will be returned.
+         * @param observer the instance of the Observer to remove
+         * @returns false if it doesn't belong to this Observable
          */
          */
         public remove(observer: Nullable<Observer<T>>): boolean {
         public remove(observer: Nullable<Observer<T>>): boolean {
             if (!observer) {
             if (!observer) {
@@ -162,8 +216,9 @@
 
 
         /**
         /**
          * Remove a callback from the Observable object
          * Remove a callback from the Observable object
-         * @param callback the callback to remove. If it doesn't belong to this Observable, false will be returned.
-         * @param scope optional scope. If used only the callbacks with this scope will be removed.
+         * @param callback the callback to remove
+         * @param scope optional scope. If used only the callbacks with this scope will be removed
+         * @returns false if it doesn't belong to this Observable
         */
         */
         public removeCallback(callback: (eventData: T, eventState: EventState) => void, scope?: any): boolean {
         public removeCallback(callback: (eventData: T, eventState: EventState) => void, scope?: any): boolean {
 
 
@@ -177,11 +232,20 @@
             return false;
             return false;
         }
         }
 
 
+        private _deferUnregister(observer: Observer<T>): void {
+            Tools.SetImmediate(() => {
+                this.remove(observer);
+            })
+        }
+
         /**
         /**
          * Notify all Observers by calling their respective callback with the given data
          * Notify all Observers by calling their respective callback with the given data
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
          * Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
-         * @param eventData
-         * @param mask
+         * @param eventData defines the data to send to all observers
+         * @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
+         * @param target defines the original target of the state
+         * @param currentTarget defines the current target of the state
+         * @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
          */
          */
         public notifyObservers(eventData: T, mask: number = -1, target?: any, currentTarget?: any): boolean {
         public notifyObservers(eventData: T, mask: number = -1, target?: any, currentTarget?: any): boolean {
             if (!this._observers.length) {
             if (!this._observers.length) {
@@ -202,6 +266,10 @@
                     } else {
                     } else {
                         state.lastReturnValue = obs.callback(eventData, state);
                         state.lastReturnValue = obs.callback(eventData, state);
                     }
                     }
+
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }
                 }
                 }
                 if (state.skipNextObservers) {
                 if (state.skipNextObservers) {
                     return false;
                     return false;
@@ -219,8 +287,8 @@
          * 
          * 
          * @param eventData The data to be sent to each callback
          * @param eventData The data to be sent to each callback
          * @param mask is used to filter observers defaults to -1
          * @param mask is used to filter observers defaults to -1
-         * @param target the callback target (see EventState)
-         * @param currentTarget The current object in the bubbling phase
+         * @param target defines the callback target (see EventState)
+         * @param currentTarget defines he current object in the bubbling phase
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          * @returns {Promise<T>} will return a Promise than resolves when all callbacks executed successfully.
          */
          */
         public notifyObserversWithPromise(eventData: T, mask: number = -1, target?: any, currentTarget?: any): Promise<T> {
         public notifyObserversWithPromise(eventData: T, mask: number = -1, target?: any, currentTarget?: any): Promise<T> {
@@ -256,6 +324,9 @@
                             return obs.callback(eventData, state);
                             return obs.callback(eventData, state);
                         });
                         });
                     }
                     }
+                    if (obs.unregisterOnNextCall) {
+                        this._deferUnregister(obs);
+                    }                    
                 }
                 }
             });
             });
 
 
@@ -265,8 +336,9 @@
 
 
         /**
         /**
          * Notify a specific observer
          * Notify a specific observer
-         * @param eventData
-         * @param mask
+         * @param observer defines the observer to notify
+         * @param eventData defines the data to be sent to each callback
+         * @param mask is used to filter observers defaults to -1
          */
          */
         public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
         public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
             let state = this._eventState;
             let state = this._eventState;
@@ -277,7 +349,8 @@
         }
         }
 
 
         /**
         /**
-         * return true is the Observable has at least one Observer registered
+         * Gets a boolean indicating if the observable has at least one observer
+         * @returns true is the Observable has at least one Observer registered
          */
          */
         public hasObservers(): boolean {
         public hasObservers(): boolean {
             return this._observers.length > 0;
             return this._observers.length > 0;
@@ -292,8 +365,9 @@
         }
         }
 
 
         /**
         /**
-        * Clone the current observable
-        */
+         * Clone the current observable
+         * @returns a new observable
+         */
         public clone(): Observable<T> {
         public clone(): Observable<T> {
             var result = new Observable<T>();
             var result = new Observable<T>();
 
 
@@ -304,8 +378,8 @@
 
 
         /**
         /**
          * Does this observable handles observer registered with a given mask
          * Does this observable handles observer registered with a given mask
-         * @param {number} trigger - the mask to be tested
-         * @return {boolean} whether or not one observer registered with the given mask is handeled 
+         * @param mask defines the mask to be tested
+         * @return whether or not one observer registered with the given mask is handeled 
         **/
         **/
         public hasSpecificMask(mask: number = -1): boolean {
         public hasSpecificMask(mask: number = -1): boolean {
             for (var obs of this._observers) {
             for (var obs of this._observers) {

BIN
tests/validation/ReferenceImages/LightProjectionTexture.png


+ 5 - 0
tests/validation/config.json

@@ -342,6 +342,11 @@
       "functionToCall": "CreateBones2TestScene",
       "functionToCall": "CreateBones2TestScene",
       "referenceImage": "instancedBones.png",
       "referenceImage": "instancedBones.png",
       "replace": "Dude.babylon, dude.babylon"
       "replace": "Dude.babylon, dude.babylon"
+    },
+    {
+      "title": "Light Projection Texture",
+      "playgroundId": "#CQNGRK",
+      "referenceImage": "LightProjectionTexture.png"
     }
     }
   ]
   ]
 }
 }