Przeglądaj źródła

Merge branch 'master' into handleControllersWithNoLeftStick

Trevor Baron 7 lat temu
rodzic
commit
8c95161cdd

Plik diff jest za duży
+ 1104 - 1035
Playground/babylon.d.txt


+ 1 - 0
Tools/Gulp/config.json

@@ -1285,6 +1285,7 @@
             "files": [
                 "../../src/Rendering/babylon.outlineRenderer.js",
                 "../../src/Rendering/babylon.edgesRenderer.js",
+                "../../src/Rendering/babylon.lineEdgesRenderer.js",
                 "../../src/Layer/babylon.effectLayerSceneComponent.js",
                 "../../src/Layer/babylon.effectLayer.js",
                 "../../src/Layer/babylon.highlightLayer.js",

+ 13 - 0
Viewer/src/managers/observablesManager.ts

@@ -54,6 +54,15 @@ export class ObservablesManager {
      */
     public onFrameRenderedObservable: Observable<any>;
 
+    /**
+     * Will notify when VR mode is entered.
+     */
+    public onEnteringVRObservable: Observable<any>;
+    /**
+     * Will notify when VR mode is exited.
+     */
+    public onExitingVRObservable: Observable<any>;
+
     constructor() {
         this.onSceneInitObservable = new Observable();
         this.onEngineInitObservable = new Observable();
@@ -66,6 +75,8 @@ export class ObservablesManager {
         this.onViewerInitStartedObservable = new Observable();
         this.onLoaderInitObservable = new Observable();
         this.onFrameRenderedObservable = new Observable();
+        this.onEnteringVRObservable = new Observable();
+        this.onExitingVRObservable = new Observable();
     }
 
     dispose() {
@@ -79,6 +90,8 @@ export class ObservablesManager {
         this.onViewerInitDoneObservable.clear();
         this.onLoaderInitObservable.clear();
         this.onFrameRenderedObservable.clear();
+        this.onEnteringVRObservable.clear();
+        this.onExitingVRObservable.clear();
     }
 
 }

+ 10 - 0
Viewer/src/managers/sceneManager.ts

@@ -798,6 +798,16 @@ export class SceneManager {
                 });
             });
         }
+        this._vrHelper.onEnteringVRObservable.add(() => {
+            if (this._observablesManager) {
+                this._observablesManager.onEnteringVRObservable.notifyObservers(this);
+            }
+        });
+        this._vrHelper.onExitingVRObservable.add(() => {
+            if (this._observablesManager) {
+                this._observablesManager.onExitingVRObservable.notifyObservers(this);
+            }
+        });
         this.onVRConfiguredObservable.notifyObservers({
             sceneManager: this,
             object: this._vrHelper,

+ 41 - 22
Viewer/src/viewer/viewer.ts

@@ -1,4 +1,4 @@
-import { Database, Effect, Engine, ISceneLoaderPlugin, ISceneLoaderPluginAsync, Observable, RenderingManager, Scene, SceneLoaderProgressEvent, TargetCamera, Tools, Vector3 } from 'babylonjs';
+import { Database, Effect, Engine, ISceneLoaderPlugin, ISceneLoaderPluginAsync, Observable, RenderingManager, Scene, SceneLoaderProgressEvent, TargetCamera, Tools, Vector3, Observer } from 'babylonjs';
 import { IModelConfiguration, IObserversConfiguration, ViewerConfiguration } from '../configuration/';
 import { processConfigurationCompatibility } from '../configuration/configurationCompatibility';
 import { ConfigurationContainer } from '../configuration/configurationContainer';
@@ -121,6 +121,19 @@ export abstract class AbstractViewer {
         return this.observablesManager.onFrameRenderedObservable;
     }
 
+    /**
+     * Observers registered here will be executed when VR more is entered.
+     */
+    public get onEnteringVRObservable(): Observable<AbstractViewer> {
+        return this.observablesManager.onEnteringVRObservable;
+    }
+    /**
+     * Observers registered here will be executed when VR mode is exited.
+     */
+    public get onExitingVRObservable(): Observable<AbstractViewer> {
+        return this.observablesManager.onExitingVRObservable;
+    }
+
     public observablesManager: ObservablesManager;
 
     /**
@@ -270,11 +283,13 @@ export abstract class AbstractViewer {
         }
 
         if (this.sceneManager.vrHelper && !this.sceneManager.vrHelper.isInVRMode) {
+
             // make sure the floor is set
             if (this.sceneManager.environmentHelper && this.sceneManager.environmentHelper.ground) {
                 this.sceneManager.vrHelper.addFloorMesh(this.sceneManager.environmentHelper.ground);
             }
 
+            this._vrToggled = true;
             this.sceneManager.vrHelper.enterVR();
 
             // position the vr camera to be in front of the object or wherever the user has configured it to be
@@ -337,29 +352,33 @@ export abstract class AbstractViewer {
     protected _initVR() {
 
         if (this.sceneManager.vrHelper) {
-            this.sceneManager.vrHelper.onExitingVR.add(() => {
-                // undo the scaling of the model
-                if (this.sceneManager.models.length) {
-                    this.sceneManager.models[0].rootMesh.scaling.scaleInPlace(1 / this._vrScale);
-                    this.sceneManager.models[0].rootMesh.position.y -= this._vrModelRepositioning;
-                }
+            this.observablesManager.onExitingVRObservable.add(() => {
+                if (this._vrToggled) {
+                    this._vrToggled = false;
+                    
+                    // undo the scaling of the model
+                    if (this.sceneManager.models.length) {
+                        this.sceneManager.models[0].rootMesh.scaling.scaleInPlace(1 / this._vrScale);
+                        this.sceneManager.models[0].rootMesh.position.y -= this._vrModelRepositioning;
+                    }
 
-                // undo the scaling of the environment
-                if (this.sceneManager.environmentHelper) {
-                    this.sceneManager.environmentHelper.ground && this.sceneManager.environmentHelper.ground.scaling.scaleInPlace(1 / this._vrScale);
-                    this.sceneManager.environmentHelper.skybox && this.sceneManager.environmentHelper.skybox.scaling.scaleInPlace(1 / this._vrScale);
-                }
+                    // undo the scaling of the environment
+                    if (this.sceneManager.environmentHelper) {
+                        this.sceneManager.environmentHelper.ground && this.sceneManager.environmentHelper.ground.scaling.scaleInPlace(1 / this._vrScale);
+                        this.sceneManager.environmentHelper.skybox && this.sceneManager.environmentHelper.skybox.scaling.scaleInPlace(1 / this._vrScale);
+                    }
 
-                // post processing
-                if (this.sceneManager.defaultRenderingPipelineEnabled && this.sceneManager.defaultRenderingPipeline) {
-                    this.sceneManager.defaultRenderingPipeline.imageProcessingEnabled = true;
-                    this.sceneManager.defaultRenderingPipeline.prepare();
-                }
+                    // post processing
+                    if (this.sceneManager.defaultRenderingPipelineEnabled && this.sceneManager.defaultRenderingPipeline) {
+                        this.sceneManager.defaultRenderingPipeline.imageProcessingEnabled = true;
+                        this.sceneManager.defaultRenderingPipeline.prepare();
+                    }
 
-                // clear set height and eidth
-                this.canvas.removeAttribute("height");
-                this.canvas.removeAttribute("width");
-                this.engine.resize();
+                    // clear set height and eidth
+                    this.canvas.removeAttribute("height");
+                    this.canvas.removeAttribute("width");
+                    this.engine.resize();
+                }
             })
         }
 
@@ -531,7 +550,7 @@ export abstract class AbstractViewer {
     }
 
     /**
-     * Dispoe the entire viewer including the scene and the engine
+     * Dispose the entire viewer including the scene and the engine
      */
     public dispose() {
         if (this._isDisposed) {

Plik diff jest za duży
+ 8 - 8
dist/draco_decoder_gltf.js


Plik diff jest za duży
+ 126 - 112
dist/draco_wasm_wrapper_gltf.js


Plik diff jest za duży
+ 1104 - 1035
dist/preview release/babylon.d.ts


Plik diff jest za duży
+ 1 - 1
dist/preview release/babylon.js


Plik diff jest za duży
+ 226 - 44
dist/preview release/babylon.max.js


Plik diff jest za duży
+ 226 - 44
dist/preview release/babylon.no-module.max.js


Plik diff jest za duży
+ 1 - 1
dist/preview release/babylon.worker.js


BIN
dist/preview release/draco_decoder_gltf.wasm


Plik diff jest za duży
+ 228 - 46
dist/preview release/es6.js


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

@@ -1,7 +1,7 @@
 {
-  "errors": 4026,
+  "errors": 4019,
   "babylon.typedoc.json": {
-    "errors": 4026,
+    "errors": 4019,
     "AbstractMesh": {
       "Property": {
         "showBoundingBox": {
@@ -4700,35 +4700,6 @@
       }
     },
     "EdgesRenderer": {
-      "Class": {
-        "Comments": {
-          "MissingText": true
-        }
-      },
-      "Constructor": {
-        "new EdgesRenderer": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "source": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "epsilon": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "checkVerticesInsteadOfIndices": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      },
       "Property": {
         "edgesWidthScalerForOrthographic": {
           "Comments": {
@@ -4742,16 +4713,6 @@
         }
       },
       "Method": {
-        "_generateEdgesLines": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "dispose": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
         "render": {
           "Comments": {
             "MissingText": true

+ 17 - 80
dist/preview release/viewer/babylon.viewer.d.ts

@@ -279,6 +279,14 @@ declare module BabylonViewer {
                 * Functions added to this observable will be executed on each frame rendered.
                 */
             readonly onFrameRenderedObservable: BABYLON.Observable<AbstractViewer>;
+            /**
+                * Observers registered here will be executed when VR more is entered.
+                */
+            readonly onEnteringVRObservable: BABYLON.Observable<AbstractViewer>;
+            /**
+                * Observers registered here will be executed when VR mode is exited.
+                */
+            readonly onExitingVRObservable: BABYLON.Observable<AbstractViewer>;
             observablesManager: ObservablesManager;
             /**
                 * The canvas associated with this viewer
@@ -374,7 +382,7 @@ declare module BabylonViewer {
                 */
             protected _configureObservers(observersConfiguration: IObserversConfiguration): void;
             /**
-                * Dispoe the entire viewer including the scene and the engine
+                * Dispose the entire viewer including the scene and the engine
                 */
             dispose(): void;
             /**
@@ -474,37 +482,6 @@ declare module BabylonViewer {
     export const telemetryManager: TelemetryManager;
 }
 declare module BabylonViewer {
-    /**
-        * An instance of the class is in charge of loading the model correctly.
-        * This class will continously be expended with tasks required from the specific loaders Babylon has.
-        *
-        * A Model loader is unique per (Abstract)Viewer. It is being generated by the viewer
-        */
-    export class ModelLoader {
-            readonly baseUrl: string;
-            /**
-                * Create a new Model loader
-                * @param _viewer the viewer using this model loader
-                */
-            constructor(_observablesManager: ObservablesManager, _configurationContainer?: ConfigurationContainer | undefined);
-            /**
-                * Adds a new plugin to the loader process.
-                *
-                * @param plugin the plugin name or the plugin itself
-                */
-            addPlugin(plugin: ILoaderPlugin | string): void;
-            /**
-                * Load a model using predefined configuration
-                * @param modelConfiguration the modelConfiguration to use to load the model
-                */
-            load(modelConfiguration: IModelConfiguration): ViewerModel;
-            cancelLoad(model: ViewerModel): void;
-            /**
-                * dispose the model loader.
-                * If loaders are registered and are in the middle of loading, they will be disposed and the request(s) will be cancelled.
-                */
-            dispose(): void;
-    }
 }
 declare module BabylonViewer {
     /**
@@ -1277,6 +1254,14 @@ declare module BabylonViewer {
                 * Functions added to this observable will be executed on each frame rendered.
                 */
             onFrameRenderedObservable: BABYLON.Observable<any>;
+            /**
+                * Will notify when VR mode is entered.
+                */
+            onEnteringVRObservable: BABYLON.Observable<any>;
+            /**
+                * Will notify when VR mode is exited.
+                */
+            onExitingVRObservable: BABYLON.Observable<any>;
             constructor();
             dispose(): void;
     }
@@ -1529,19 +1514,6 @@ declare module BabylonViewer {
     }
 }
 declare module BabylonViewer {
-    /**
-        * Get a loader plugin according to its name.
-        * The plugin will be cached and will be reused if called for again.
-        *
-        * @param name the name of the plugin
-        */
-    export function getLoaderPluginByName(name: string): ILoaderPlugin;
-    /**
-        *
-        */
-    export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
-}
-declare module BabylonViewer {
 }
 declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {
@@ -1677,41 +1649,6 @@ declare module BabylonViewer {
     }
 }
 declare module BabylonViewer {
-    export class TelemetryLoaderPlugin implements ILoaderPlugin {
-        onInit(loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onLoaded(model: ViewerModel): void;
-        onError(message: string, exception: any): void;
-        onComplete(): void;
-    }
-}
-declare module BabylonViewer {
-    /**
-      * A loder plugin to use MSFT_lod extension correctly (glTF)
-      */
-    export class MSFTLodLoaderPlugin implements ILoaderPlugin {
-        onInit(loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onExtensionLoaded(extension: BABYLON.IGLTFLoaderExtension): void;
-    }
-}
-declare module BabylonViewer {
-    /**
-      * Force-apply material configuration right after a material was loaded.
-      */
-    export class ApplyMaterialConfigPlugin implements ILoaderPlugin {
-        onInit(loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onMaterialLoaded(material: BABYLON.Material): void;
-    }
-}
-declare module BabylonViewer {
-    /**
-      * A (PBR) material will be extended using this function.
-      * This function will hold extra default configuration for the viewer, if not implemented in Babylon itself.
-      */
-    export class ExtendedMaterialLoaderPlugin implements ILoaderPlugin {
-        onMaterialLoaded(baseMaterial: BABYLON.Material): void;
-    }
-}
-declare module BabylonViewer {
     export interface ICameraConfiguration {
         position?: {
             x: number;

Plik diff jest za duży
+ 2 - 2
dist/preview release/viewer/babylon.viewer.js


Plik diff jest za duży
+ 4 - 4
dist/preview release/viewer/babylon.viewer.max.js


+ 18 - 108
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -321,6 +321,14 @@ declare module 'babylonjs-viewer/viewer/viewer' {
                 * Functions added to this observable will be executed on each frame rendered.
                 */
             readonly onFrameRenderedObservable: Observable<AbstractViewer>;
+            /**
+                * Observers registered here will be executed when VR more is entered.
+                */
+            readonly onEnteringVRObservable: Observable<AbstractViewer>;
+            /**
+                * Observers registered here will be executed when VR mode is exited.
+                */
+            readonly onExitingVRObservable: Observable<AbstractViewer>;
             observablesManager: ObservablesManager;
             /**
                 * The canvas associated with this viewer
@@ -416,7 +424,7 @@ declare module 'babylonjs-viewer/viewer/viewer' {
                 */
             protected _configureObservers(observersConfiguration: IObserversConfiguration): void;
             /**
-                * Dispoe the entire viewer including the scene and the engine
+                * Dispose the entire viewer including the scene and the engine
                 */
             dispose(): void;
             /**
@@ -519,42 +527,7 @@ declare module 'babylonjs-viewer/managers/telemetryManager' {
 }
 
 declare module 'babylonjs-viewer/loader/modelLoader' {
-    import { ConfigurationContainer } from 'babylonjs-viewer/configuration/configurationContainer';
-    import { IModelConfiguration } from 'babylonjs-viewer/configuration/interfaces/modelConfiguration';
-    import { ObservablesManager } from 'babylonjs-viewer/managers/observablesManager';
-    import { ViewerModel } from 'babylonjs-viewer/model/viewerModel';
-    import { ILoaderPlugin } from 'babylonjs-viewer/loader/plugins';
-    /**
-        * An instance of the class is in charge of loading the model correctly.
-        * This class will continously be expended with tasks required from the specific loaders Babylon has.
-        *
-        * A Model loader is unique per (Abstract)Viewer. It is being generated by the viewer
-        */
-    export class ModelLoader {
-            readonly baseUrl: string;
-            /**
-                * Create a new Model loader
-                * @param _viewer the viewer using this model loader
-                */
-            constructor(_observablesManager: ObservablesManager, _configurationContainer?: ConfigurationContainer | undefined);
-            /**
-                * Adds a new plugin to the loader process.
-                *
-                * @param plugin the plugin name or the plugin itself
-                */
-            addPlugin(plugin: ILoaderPlugin | string): void;
-            /**
-                * Load a model using predefined configuration
-                * @param modelConfiguration the modelConfiguration to use to load the model
-                */
-            load(modelConfiguration: IModelConfiguration): ViewerModel;
-            cancelLoad(model: ViewerModel): void;
-            /**
-                * dispose the model loader.
-                * If loaders are registered and are in the middle of loading, they will be disposed and the request(s) will be cancelled.
-                */
-            dispose(): void;
-    }
+    
 }
 
 declare module 'babylonjs-viewer/model/viewerModel' {
@@ -1364,6 +1337,14 @@ declare module 'babylonjs-viewer/managers/observablesManager' {
                 * Functions added to this observable will be executed on each frame rendered.
                 */
             onFrameRenderedObservable: Observable<any>;
+            /**
+                * Will notify when VR mode is entered.
+                */
+            onEnteringVRObservable: Observable<any>;
+            /**
+                * Will notify when VR mode is exited.
+                */
+            onExitingVRObservable: Observable<any>;
             constructor();
             dispose(): void;
     }
@@ -1626,26 +1607,6 @@ declare module 'babylonjs-viewer/configuration/interfaces/modelConfiguration' {
     }
 }
 
-declare module 'babylonjs-viewer/loader/plugins' {
-    import { TelemetryLoaderPlugin } from "babylonjs-viewer/loader/plugins/telemetryLoaderPlugin";
-    import { ILoaderPlugin } from "babylonjs-viewer/loader/plugins/loaderPlugin";
-    import { MSFTLodLoaderPlugin } from 'babylonjs-viewer/loader/plugins/msftLodLoaderPlugin';
-    import { ApplyMaterialConfigPlugin } from 'babylonjs-viewer/loader/plugins/applyMaterialConfig';
-    import { ExtendedMaterialLoaderPlugin } from 'babylonjs-viewer/loader/plugins/extendedMaterialLoaderPlugin';
-    export { TelemetryLoaderPlugin, ILoaderPlugin, MSFTLodLoaderPlugin, ApplyMaterialConfigPlugin, ExtendedMaterialLoaderPlugin };
-    /**
-        * Get a loader plugin according to its name.
-        * The plugin will be cached and will be reused if called for again.
-        *
-        * @param name the name of the plugin
-        */
-    export function getLoaderPluginByName(name: string): ILoaderPlugin;
-    /**
-        *
-        */
-    export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';
@@ -1804,57 +1765,6 @@ declare module 'babylonjs-viewer/configuration/interfaces/modelAnimationConfigur
     }
 }
 
-declare module 'babylonjs-viewer/loader/plugins/telemetryLoaderPlugin' {
-    import { ILoaderPlugin } from "babylonjs-viewer/loader/plugins/loaderPlugin";
-    import { ViewerModel } from "babylonjs-viewer/model/viewerModel";
-    import { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from "babylonjs";
-    export class TelemetryLoaderPlugin implements ILoaderPlugin {
-        onInit(loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onLoaded(model: ViewerModel): void;
-        onError(message: string, exception: any): void;
-        onComplete(): void;
-    }
-}
-
-declare module 'babylonjs-viewer/loader/plugins/msftLodLoaderPlugin' {
-    import { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from 'babylonjs';
-    import { IGLTFLoaderExtension } from 'babylonjs-loaders';
-    import { ViewerModel } from 'babylonjs-viewer/model/viewerModel';
-    import { ILoaderPlugin } from 'babylonjs-viewer/loader/plugins/loaderPlugin';
-    /**
-      * A loder plugin to use MSFT_lod extension correctly (glTF)
-      */
-    export class MSFTLodLoaderPlugin implements ILoaderPlugin {
-        onInit(loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onExtensionLoaded(extension: IGLTFLoaderExtension): void;
-    }
-}
-
-declare module 'babylonjs-viewer/loader/plugins/applyMaterialConfig' {
-    import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, Material } from 'babylonjs';
-    import { ViewerModel } from 'babylonjs-viewer/model/viewerModel';
-    import { ILoaderPlugin } from 'babylonjs-viewer/loader/plugins/loaderPlugin';
-    /**
-      * Force-apply material configuration right after a material was loaded.
-      */
-    export class ApplyMaterialConfigPlugin implements ILoaderPlugin {
-        onInit(loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync, model: ViewerModel): void;
-        onMaterialLoaded(material: Material): void;
-    }
-}
-
-declare module 'babylonjs-viewer/loader/plugins/extendedMaterialLoaderPlugin' {
-    import { Material } from 'babylonjs';
-    import { ILoaderPlugin } from 'babylonjs-viewer/loader/plugins/loaderPlugin';
-    /**
-      * A (PBR) material will be extended using this function.
-      * This function will hold extra default configuration for the viewer, if not implemented in Babylon itself.
-      */
-    export class ExtendedMaterialLoaderPlugin implements ILoaderPlugin {
-        onMaterialLoaded(baseMaterial: Material): void;
-    }
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces/cameraConfiguration' {
     export interface ICameraConfiguration {
         position?: {

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

@@ -7,7 +7,7 @@
   - New GUI control: [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
   - New GUI control: [InputPassword](https://doc.babylonjs.com/how_to/gui#inputpassword) ([theom](https://github.com/theom))
 - Gizmo Support ([TrevorDev](https://github.com/TrevorDev))
-  - Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include: position, rotation, scale and bounding box. [Doc](http://doc.babylonjs.com/how_to/gizmo) ([TrevorDev](https://github.com/TrevorDev))
+  - Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include: position, scale, rotation and bounding box. [Doc](http://doc.babylonjs.com/how_to/gizmo) ([TrevorDev](https://github.com/TrevorDev))
   - New behaviors: PointerDragBehavior, SixDofDragBehavior and MultiPointerScaleBehavior to enable smooth drag and drop/scaling with mouse or 6dof controller on a mesh. [Doc](http://doc.babylonjs.com/how_to/meshbehavior) ([TrevorDev](https://github.com/TrevorDev))
   - Added attachToBoxBehavior to attach UI to a bounding box ([TrevorDev](https://github.com/TrevorDev))
   - Gizmo manager's internal gizmos are now public ([TrevorDev](https://github.com/TrevorDev))
@@ -49,6 +49,7 @@
 - All NPM packages have `latest`and `preview` streams [#3055](https://github.com/BabylonJS/Babylon.js/issues/3055) ([RaananW](https://github.com/RaananW))
 - Added New Tools Tab in the inspector (env texture and screenshot tools so far) ([sebavan](http://www.github.com/sebavan))
 - Moved to gulp 4, updated dependencies to latest ([RaananW](https://github.com/RaananW))
+- Added EdgesLineRenderer to address [#4919](https://github.com/BabylonJS/Babylon.js/pull/4919) ([barteq100](https://github.com/barteq100))
 
 ### GUI
 - Added dead key support and before key add observable to InputText. [Doc](https://doc.babylonjs.com/how_to/gui#using-onbeforekeyaddobservable-for-extended-keyboard-layouts-and-input-masks)([theom](https://github.com/theom))
@@ -152,10 +153,12 @@
 - Fixed issue where gaze trackers were appearing even after leaving VR ([atulyar](https://github.com/atulyar))
 - AdvancedDynamicTexture should not overwrite skipOnPointerObservable to false ([TrevorDev](https://github.com/TrevorDev))
 - Fixed issue where VRExperienceHelper.onExitingVR observable was being fired twice ([atulyar](https://github.com/atulyar))
+- Avoid firing onExitingVR observable multiple times when calling exitVR() and add observables to Viewer that can be used instead of the ones in VRExperienceHelper ([atulyar](https://github.com/atulyar))
 - GizmoManager should hide existing gizmos if a non-attachable mesh is selected ([TrevorDev](https://github.com/TrevorDev))
 - Ignore isPickable = false for vr ray casting if the mesh's name matches the specified floorMeshName to maintain backwards compatability ([TrevorDev](https://github.com/TrevorDev))
 - Fix File Loading if hosted from `file:`-Protocol ([ltetzlaff](https://github.com/ltetzlaff))
 - Do not throw error when updating a controller with no left stick ([TrevorDev](https://github.com/TrevorDev))
+- Exiting VR can result in messed up view ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 

+ 48 - 40
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -254,8 +254,10 @@ module BABYLON {
         private _webVRready = false;
         // Are we waiting for the requestPresent callback to complete?
         private _webVRrequesting = false;
-        // Are we presenting to the headset right now?
+        // Are we presenting to the headset right now? (this is the vrDevice state)
         private _webVRpresenting = false;
+        // Have we entered VR? (this is the VRExperienceHelper state)
+        private _hasEnteredVR: boolean;
 
         // Are we presenting in the fullscreen fallback?
         private _fullscreenVRpresenting = false;
@@ -566,6 +568,8 @@ module BABYLON {
                 this._defaultHeight *= webVROptions.positionScale;
             }
 
+            this._hasEnteredVR = false;
+
             // Set position
             if (this._scene.activeCamera) {
                 this._position = this._scene.activeCamera.position.clone();
@@ -773,11 +777,9 @@ module BABYLON {
             var vrDisplay = this._scene.getEngine().getVRDevice();
             if (vrDisplay) {
                 var wasPresenting = this._webVRpresenting;
-
-                // A VR display is connected
                 this._webVRpresenting = vrDisplay.isPresenting;
-
-                if (wasPresenting && !this._webVRpresenting && this.isInVRMode)
+                
+                if (wasPresenting && !this._webVRpresenting)
                     this.exitVR();
             } else {
                 Tools.Warn('Detected VRDisplayPresentChange on an unknown VRDisplay. Did you can enterVR on the vrExperienceHelper?');
@@ -869,54 +871,60 @@ module BABYLON {
             if (this._interactionsEnabled) {
                 this._scene.registerBeforeRender(this.beforeRender);
             }
+
+            this._hasEnteredVR = true;
         }
 
         /**
          * Attempt to exit VR, or fullscreen.
          */
         public exitVR() {
-            if (this.onExitingVRObservable) {
-                try {
-                    this.onExitingVRObservable.notifyObservers(this);
+            if (this._hasEnteredVR) {
+                if (this.onExitingVRObservable) {
+                    try {
+                        this.onExitingVRObservable.notifyObservers(this);
+                    }
+                    catch (err) {
+                        Tools.Warn("Error in your custom logic onExitingVR: " + err);
+                    }
                 }
-                catch (err) {
-                    Tools.Warn("Error in your custom logic onExitingVR: " + err);
+                if (this._webVRpresenting) {
+                    this._scene.getEngine().disableVR();
                 }
-            }
-            if (this._webVRpresenting) {
-                this._scene.getEngine().disableVR();
-            }
-            if (this._scene.activeCamera) {
-                this._position = this._scene.activeCamera.position.clone();
-
-            }
+                if (this._scene.activeCamera) {
+                    this._position = this._scene.activeCamera.position.clone();
 
-            if (this._deviceOrientationCamera) {
-                this._deviceOrientationCamera.position = this._position;
-                this._scene.activeCamera = this._deviceOrientationCamera;
-                if (this._canvas) {
-                    this._scene.activeCamera.attachControl(this._canvas);
                 }
-            } else if (this._existingCamera) {
-                this._existingCamera.position = this._position;
-                this._scene.activeCamera = this._existingCamera;
-            }
-
-            this.updateButtonVisibility();
-
-            if (this._interactionsEnabled) {
-                this._scene.unregisterBeforeRender(this.beforeRender);
-                this._cameraGazer._gazeTracker.isVisible = false;
-                if (this._leftController) {
-                    this._leftController._gazeTracker.isVisible = false;
+    
+                if (this._deviceOrientationCamera) {
+                    this._deviceOrientationCamera.position = this._position;
+                    this._scene.activeCamera = this._deviceOrientationCamera;
+                    if (this._canvas) {
+                        this._scene.activeCamera.attachControl(this._canvas);
+                    }
+                } else if (this._existingCamera) {
+                    this._existingCamera.position = this._position;
+                    this._scene.activeCamera = this._existingCamera;
                 }
-                if (this._rightController) {
-                    this._rightController._gazeTracker.isVisible = false;
+    
+                this.updateButtonVisibility();
+    
+                if (this._interactionsEnabled) {
+                    this._scene.unregisterBeforeRender(this.beforeRender);
+                    this._cameraGazer._gazeTracker.isVisible = false;
+                    if (this._leftController) {
+                        this._leftController._gazeTracker.isVisible = false;
+                    }
+                    if (this._rightController) {
+                        this._rightController._gazeTracker.isVisible = false;
+                    }
                 }
-            }
+    
+                // resize to update width and height when exiting vr exits fullscreen
+                this._scene.getEngine().resize();
 
-            // resize to update width and height when exiting vr exits fullscreen
-            this._scene.getEngine().resize();
+                this._hasEnteredVR = false;
+            }
         }
 
         /**

+ 12 - 0
src/Cameras/VR/babylon.webVRCamera.ts

@@ -346,6 +346,7 @@ module BABYLON {
          * Disposes the camera
          */
         public dispose(): void {
+            this._detachIfAttached();
             this.getEngine().onVRRequestPresentComplete.removeCallback(this._onVREnabled);
             super.dispose();
         }
@@ -442,6 +443,14 @@ module BABYLON {
             }
         }
 
+        private _htmlElementAttached:Nullable<HTMLElement> = null;
+        private _detachIfAttached = ()=>{
+            var vrDisplay = this.getEngine().getVRDevice();
+            if (vrDisplay && !vrDisplay.isPresenting && this._htmlElementAttached) {
+                this.detachControl(this._htmlElementAttached);
+            }
+        }
+
         /**
          * WebVR's attach control will start broadcasting frames to the device.
          * Note that in certain browsers (chrome for example) this function must be called
@@ -454,12 +463,14 @@ module BABYLON {
         public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
             super.attachControl(element, noPreventDefault);
             this._attached = true;
+            this._htmlElementAttached = element;
 
             noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
 
             if (this._vrDevice) {
                 this.getEngine().enableVR();
             }
+            window.addEventListener('vrdisplaypresentchange', this._detachIfAttached);
         }
 
         /**
@@ -474,6 +485,7 @@ module BABYLON {
             super.detachControl(element);
             this._attached = false;
             this.getEngine().disableVR();
+            window.removeEventListener('vrdisplaypresentchange', this._detachIfAttached);
         }
 
         /**

+ 4 - 4
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -272,11 +272,11 @@ module BABYLON {
                 })
         }
 
-        private _recurseComputeWorld(mesh: AbstractMesh) {
-            mesh.computeWorldMatrix(true);
+        private _recurseComputeWorld(node: Node) {
+            node.computeWorldMatrix(true);
             if(!this.ignoreChildren){
-                mesh.getChildMeshes().forEach((m) => {
-                    this._recurseComputeWorld(m);
+                node.getDescendants().forEach((n) => {
+                    this._recurseComputeWorld(n);
                 });
             }
         }

+ 14 - 0
src/Mesh/babylon.linesMesh.ts

@@ -139,5 +139,19 @@
         public clone(name: string, newParent?: Node, doNotCloneChildren?: boolean): LinesMesh {
             return new LinesMesh(name, this.getScene(), newParent, this, doNotCloneChildren);
         }
+
+        /**
+         * Enables the edge rendering mode on the mesh.
+         * This mode makes the mesh edges visible
+         * @param epsilon defines the maximal distance between two angles to detect a face
+         * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
+         * @returns the currentAbstractMesh
+         * @see https://www.babylonjs-playground.com/#19O9TU#0
+         */
+        public enableEdgesRendering(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
+            this.disableEdgesRendering();
+            this._edgesRenderer = new LineEdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
+            return this;
+        }
     }
 } 

+ 49 - 19
src/Rendering/babylon.edgesRenderer.ts

@@ -1,5 +1,7 @@
 module BABYLON {
-
+    /**
+     * FaceAdjacencies Helper class to generate edges
+     */
     class FaceAdjacencies {
         public edges = new Array<number>();
         public p0: Vector3;
@@ -8,36 +10,48 @@
         public edgesConnectedCount = 0;
     }
 
+    /**
+     * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
+     */
     export class EdgesRenderer {
         public edgesWidthScalerForOrthographic = 1000.0;
         public edgesWidthScalerForPerspective = 50.0;
-        private _source: AbstractMesh;
-        private _linesPositions = new Array<number>();
-        private _linesNormals = new Array<number>();
-        private _linesIndices = new Array<number>();
-        private _epsilon: number;
-        private _indicesCount: number;
-
-        private _lineShader: ShaderMaterial;
-        private _ib: WebGLBuffer;
-        private _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
-        private _checkVerticesInsteadOfIndices = false;
+        protected _source: AbstractMesh;
+        protected _linesPositions = new Array<number>();
+        protected _linesNormals = new Array<number>();
+        protected _linesIndices = new Array<number>();
+        protected _epsilon: number;
+        protected _indicesCount: number;
+
+        protected _lineShader: ShaderMaterial;
+        protected _ib: WebGLBuffer;
+        protected _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
+        protected _checkVerticesInsteadOfIndices = false;
 
         /** Gets or sets a boolean indicating if the edgesRenderer is active */
         public isEnabled = true;
 
-        // Beware when you use this class with complex objects as the adjacencies computation can be really long
-        constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
+        /**
+         * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
+         * Beware when you use this class with complex objects as the adjacencies computation can be really long
+         * @param  source Mesh used to create edges
+         * @param  epsilon sum of angles in adjacency to check for edge
+         * @param  checkVerticesInsteadOfIndices
+         * @param  generateEdgesLines - should generate Lines or only prepare resources.
+         */
+        constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true) {
             this._source = source;
             this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
 
             this._epsilon = epsilon;
 
             this._prepareRessources();
-            this._generateEdgesLines();
+            if(generateEdgesLines) {
+                this._generateEdgesLines();
+            }
         }
 
-        private _prepareRessources(): void {
+        protected _prepareRessources(): void {
             if (this._lineShader) {
                 return;
             }
@@ -69,6 +83,9 @@
             this._ib = engine.createIndexBuffer(this._linesIndices);
         }
 
+        /**
+         * Releases the required resources for the edges renderer
+         */
         public dispose(): void {
 
             var buffer = this._buffers[VertexBuffer.PositionKind];
@@ -86,7 +103,7 @@
             this._lineShader.dispose();
         }
 
-        private _processEdgeForAdjacencies(pa: number, pb: number, p0: number, p1: number, p2: number): number {
+        protected _processEdgeForAdjacencies(pa: number, pb: number, p0: number, p1: number, p2: number): number {
             if (pa === p0 && pb === p1 || pa === p1 && pb === p0) {
                 return 0;
             }
@@ -102,7 +119,7 @@
             return -1;
         }
 
-        private _processEdgeForAdjacenciesWithVertices(pa: Vector3, pb: Vector3, p0: Vector3, p1: Vector3, p2: Vector3): number {
+        protected _processEdgeForAdjacenciesWithVertices(pa: Vector3, pb: Vector3, p0: Vector3, p1: Vector3, p2: Vector3): number {
             if (pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p1) || pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p0)) {
                 return 0;
             }
@@ -118,7 +135,16 @@
             return -1;
         }
 
-        private _checkEdge(faceIndex: number, edge: number, faceNormals: Array<Vector3>, p0: Vector3, p1: Vector3): void {
+        /**
+         * Checks if the pair of p0 and p1 is en edge
+         * @param faceIndex
+         * @param edge
+         * @param faceNormals
+         * @param  p0
+         * @param  p1
+         * @private
+         */
+        protected _checkEdge(faceIndex: number, edge: number, faceNormals: Array<Vector3>, p0: Vector3, p1: Vector3): void {
             var needToCreateLine;
 
             if (edge === undefined) {
@@ -182,6 +208,10 @@
             }
         }
 
+        /**
+         * Generates lines edges from adjacencjes
+         * @private
+         */
         _generateEdgesLines(): void {
             var positions = this._source.getVerticesData(VertexBuffer.PositionKind);
             var indices = this._source.getIndices();

+ 127 - 0
src/Rendering/babylon.lineEdgesRenderer.ts

@@ -0,0 +1,127 @@
+module BABYLON {
+    /**
+     * FaceAdjacencies Helper class to generate edges
+     */
+    class FaceAdjacencies {
+        public edges = new Array<number>();
+        public p0: Vector3;
+        public p1: Vector3;
+        public edgesConnectedCount = 0;
+    }
+
+    /**
+     * LineEdgesRenderer for LineMeshes to remove unnecessary triangulation
+     */
+    export class LineEdgesRenderer extends EdgesRenderer {
+
+        /**
+         * This constructor turns off auto generating edges line in Edges Renderer to make it here.
+         * @param  source LineMesh used to generate edges
+         * @param  epsilon not important (specified angle for edge detection)
+         * @param  checkVerticesInsteadOfIndices not important for LineMesh
+         */
+        constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
+                super(source, epsilon, checkVerticesInsteadOfIndices, false);
+                this._generateEdgesLines();
+        }
+
+        /**
+         * Always create the edge since its a line so only important things are p0 and p1
+         * @param  faceIndex not important for LineMesh
+         * @param  edge not important for LineMesh
+         * @param  faceNormals not important for LineMesh
+         * @param  p0 beginnig of line
+         * @param  p1 end of line
+         */
+        protected _checkEdge(faceIndex: number, edge: number, faceNormals: Array<Vector3>, p0: Vector3, p1: Vector3): void {
+                var offset = this._linesPositions.length / 3;
+                var normal = p0.subtract(p1);
+                normal.normalize();
+
+                // Positions
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+
+                // Normals
+                this._linesNormals.push(p1.x);
+                this._linesNormals.push(p1.y);
+                this._linesNormals.push(p1.z);
+                this._linesNormals.push(-1);
+
+                this._linesNormals.push(p1.x);
+                this._linesNormals.push(p1.y);
+                this._linesNormals.push(p1.z);
+                this._linesNormals.push(1);
+
+                this._linesNormals.push(p0.x);
+                this._linesNormals.push(p0.y);
+                this._linesNormals.push(p0.z);
+                this._linesNormals.push(-1);
+
+                this._linesNormals.push(p0.x);
+                this._linesNormals.push(p0.y);
+                this._linesNormals.push(p0.z);
+                this._linesNormals.push(1);
+
+                // Indices
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 1);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset + 3);
+        }
+
+        /**
+         * Generate edges for each line in LinesMesh. Every Line should be rendered as edge.
+         */
+        _generateEdgesLines(): void {
+            var positions = this._source.getVerticesData(VertexBuffer.PositionKind);
+            var indices = this._source.getIndices();
+
+            if (!indices || !positions) {
+                return;
+            }
+
+            // First let's find adjacencies
+            var adjacencies = new Array<FaceAdjacencies>();
+            var faceNormals = new Array<Vector3>();
+            var index: number;
+            for(let i=0; i<(positions.length / 3) - 1 ; i++){
+                const currentAdjecancy  = new FaceAdjacencies();
+                currentAdjecancy.p0 = new Vector3(positions[i*3], positions[i*3+1], positions[i*3+2]);
+                currentAdjecancy.p1 = new Vector3(positions[(i+1)*3], positions[(i+1)*3 + 1], positions[(i+1)*3 + 2]);
+                adjacencies.push(currentAdjecancy);
+            }
+            // Create lines
+            for (index = 0; index < adjacencies.length; index++) {
+                // We need a line when a face has no adjacency on a specific edge or if all the adjacencies has an angle greater than epsilon
+                var current = adjacencies[index];
+                this._checkEdge(index, current.edges[0], faceNormals, current.p0, current.p1);
+            }
+
+            // Merge into a single mesh
+            var engine = this._source.getScene().getEngine();
+
+            this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
+            this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
+
+            this._ib = engine.createIndexBuffer(this._linesIndices);
+
+            this._indicesCount = this._linesIndices.length;
+        }
+    }
+}