sebastien 6 years ago
parent
commit
37743aebc0
33 changed files with 17439 additions and 16571 deletions
  1. 8288 8207
      Playground/babylon.d.txt
  2. BIN
      Playground/textures/gui/backgroundImage-vertical.png
  3. BIN
      Playground/textures/gui/valueImage-vertical.png
  4. 2 1
      Tools/Gulp/config.json
  5. 8290 8211
      dist/preview release/babylon.d.ts
  6. 1 1
      dist/preview release/babylon.js
  7. 181 20
      dist/preview release/babylon.max.js
  8. 181 20
      dist/preview release/babylon.no-module.max.js
  9. 1 1
      dist/preview release/babylon.worker.js
  10. 183 22
      dist/preview release/es6.js
  11. 4 2
      dist/preview release/gui/babylon.gui.d.ts
  12. 46 26
      dist/preview release/gui/babylon.gui.js
  13. 1 1
      dist/preview release/gui/babylon.gui.js.map
  14. 1 1
      dist/preview release/gui/babylon.gui.min.js
  15. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  16. 8 4
      dist/preview release/gui/babylon.gui.module.d.ts
  17. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js.map
  18. 1 1
      dist/preview release/viewer/babylon.viewer.js
  19. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  20. 4 1
      dist/preview release/what's new.md
  21. 20 3
      gui/src/2D/controls/baseSlider.ts
  22. 23 7
      gui/src/2D/controls/imageBasedSlider.ts
  23. 1 16
      gui/src/2D/controls/slider.ts
  24. 55 7
      src/Cameras/XR/babylon.webXREnterExitUI.ts
  25. 18 8
      src/Cameras/XR/babylon.webXRExperienceHelper.ts
  26. 104 0
      src/Cameras/XR/babylon.webXRInput.ts
  27. 1 1
      src/Cameras/XR/babylon.webXRManagedOutputCanvas.ts
  28. 5 5
      src/Cameras/XR/babylon.webXRSessionManager.ts
  29. 3 1
      src/Helpers/babylon.sceneHelpers.ts
  30. 6 1
      src/Lights/Shadows/babylon.shadowGenerator.ts
  31. 2 0
      src/babylon.mixins.ts
  32. BIN
      tests/validation/ReferenceImages/Sliders.png
  33. 6 0
      tests/validation/config.json

File diff suppressed because it is too large
+ 8288 - 8207
Playground/babylon.d.txt


BIN
Playground/textures/gui/backgroundImage-vertical.png


BIN
Playground/textures/gui/valueImage-vertical.png


+ 2 - 1
Tools/Gulp/config.json

@@ -1305,7 +1305,8 @@
                 "../../src/Cameras/XR/babylon.webXRSessionManager.js",
                 "../../src/Cameras/XR/babylon.webXRExperienceHelper.js",
                 "../../src/Cameras/XR/babylon.webXREnterExitUI.js",
-                "../../src/Cameras/XR/babylon.webXRManagedOutputCanvas.js"
+                "../../src/Cameras/XR/babylon.webXRManagedOutputCanvas.js",
+                "../../src/Cameras/XR/babylon.webXRInput.js"
             ],
             "dependUpon": [
                 "core",

File diff suppressed because it is too large
+ 8290 - 8211
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.js


+ 181 - 20
dist/preview release/babylon.max.js

@@ -77037,7 +77037,7 @@ var BABYLON;
             var scene = this._scene;
             var engine = scene.getEngine();
             var material = subMesh.getMaterial();
-            if (!material) {
+            if (!material || subMesh.verticesCount === 0) {
                 return;
             }
             // Culling
@@ -77075,6 +77075,9 @@ var BABYLON;
                     var skeleton = mesh.skeleton;
                     if (skeleton.isUsingTextureForMatrices) {
                         var boneTexture = skeleton.getTransformMatrixTexture();
+                        if (!boneTexture) {
+                            return;
+                        }
                         this._effect.setTexture("boneSampler", boneTexture);
                         this._effect.setFloat("boneTextureWidth", 4.0 * (skeleton.bones.length + 1));
                     }
@@ -108609,7 +108612,7 @@ var BABYLON;
          * After initialization enterXR can be called to start an XR session
          * @returns Promise which resolves after it is initialized
          */
-        WebXRSessionManager.prototype.initialize = function () {
+        WebXRSessionManager.prototype.initializeAsync = function () {
             var _this = this;
             // Check if the browser supports webXR
             this._xrNavigator = navigator;
@@ -108628,7 +108631,7 @@ var BABYLON;
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
          */
-        WebXRSessionManager.prototype.enterXR = function (sessionCreationOptions, frameOfReferenceType) {
+        WebXRSessionManager.prototype.enterXRAsync = function (sessionCreationOptions, frameOfReferenceType) {
             var _this = this;
             // initialize session
             return this._xrDevice.requestSession(sessionCreationOptions).then(function (session) {
@@ -108669,7 +108672,7 @@ var BABYLON;
          * Stops the xrSession and restores the renderloop
          * @returns Promise which resolves after it exits XR
          */
-        WebXRSessionManager.prototype.exitXR = function () {
+        WebXRSessionManager.prototype.exitXRAsync = function () {
             return this._xrSession.end();
         };
         /**
@@ -108677,7 +108680,7 @@ var BABYLON;
          * @param ray ray to cast into the environment
          * @returns Promise which resolves with a collision point in the environment if it exists
          */
-        WebXRSessionManager.prototype.environmentPointHitTest = function (ray) {
+        WebXRSessionManager.prototype.environmentPointHitTestAsync = function (ray) {
             var _this = this;
             return new Promise(function (res, rej) {
                 // Compute left handed inputs to request hit test
@@ -108711,7 +108714,7 @@ var BABYLON;
          * @param options creation options to check if they are supported
          * @returns true if supported
          */
-        WebXRSessionManager.prototype.supportsSession = function (options) {
+        WebXRSessionManager.prototype.supportsSessionAsync = function (options) {
             return this._xrDevice.supportsSession(options).then(function () {
                 return true;
             }).catch(function (e) {
@@ -108810,7 +108813,7 @@ var BABYLON;
          */
         WebXRExperienceHelper.CreateAsync = function (scene) {
             var helper = new WebXRExperienceHelper(scene);
-            return helper._sessionManager.initialize().then(function () {
+            return helper._sessionManager.initializeAsync().then(function () {
                 helper._supported = true;
                 return helper;
             }).catch(function () {
@@ -108821,9 +108824,9 @@ var BABYLON;
          * Exits XR mode and returns the scene to its original state
          * @returns promise that resolves after xr mode has exited
          */
-        WebXRExperienceHelper.prototype.exitXR = function () {
+        WebXRExperienceHelper.prototype.exitXRAsync = function () {
             this._setState(WebXRState.EXITING_XR);
-            return this._sessionManager.exitXR();
+            return this._sessionManager.exitXRAsync();
         };
         /**
          * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
@@ -108831,10 +108834,10 @@ var BABYLON;
          * @param frameOfReference frame of reference of the XR session
          * @returns promise that resolves after xr mode has entered
          */
-        WebXRExperienceHelper.prototype.enterXR = function (sessionCreationOptions, frameOfReference) {
+        WebXRExperienceHelper.prototype.enterXRAsync = function (sessionCreationOptions, frameOfReference) {
             var _this = this;
             this._setState(WebXRState.ENTERING_XR);
-            return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(function () {
+            return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(function () {
                 // Cache pre xr scene settings
                 _this._originalSceneAutoClear = _this.scene.autoClear;
                 _this._nonVRCamera = _this.scene.activeCamera;
@@ -108859,15 +108862,23 @@ var BABYLON;
             });
         };
         /**
+         * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
+         * @param ray ray to cast into the environment
+         * @returns Promise which resolves with a collision point in the environment if it exists
+         */
+        WebXRExperienceHelper.prototype.environmentPointHitTestAsync = function (ray) {
+            return this._sessionManager.environmentPointHitTestAsync(ray);
+        };
+        /**
          * Checks if the creation options are supported by the xr session
          * @param options creation options
          * @returns true if supported
          */
-        WebXRExperienceHelper.prototype.supportsSession = function (options) {
+        WebXRExperienceHelper.prototype.supportsSessionAsync = function (options) {
             if (!this._supported) {
                 return Promise.resolve(false);
             }
-            return this._sessionManager.supportsSession(options);
+            return this._sessionManager.supportsSessionAsync(options);
         };
         /**
          * Disposes of the experience helper
@@ -108923,6 +108934,32 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
 var BABYLON;
 (function (BABYLON) {
     /**
+     * Button which can be used to enter a different mode of XR
+     */
+    var WebXREnterExitUIButton = /** @class */ (function () {
+        /**
+         * Creates a WebXREnterExitUIButton
+         * @param element button element
+         * @param initializationOptions XR initialization options for the button
+         */
+        function WebXREnterExitUIButton(
+        /** button element */
+        element, 
+        /** XR initialization options for the button */
+        initializationOptions) {
+            this.element = element;
+            this.initializationOptions = initializationOptions;
+        }
+        /**
+         * Overwritable function which can be used to update the button's visuals when the state changes
+         * @param activeButton the current active button in the UI
+         */
+        WebXREnterExitUIButton.prototype.update = function (activeButton) {
+        };
+        return WebXREnterExitUIButton;
+    }());
+    BABYLON.WebXREnterExitUIButton = WebXREnterExitUIButton;
+    /**
      * Options to create the webXR UI
      */
     var WebXREnterExitUIOptions = /** @class */ (function () {
@@ -108939,6 +108976,7 @@ var BABYLON;
             var _this = this;
             this.scene = scene;
             this._buttons = [];
+            this._activeButton = null;
             this._overlay = document.createElement("div");
             this._overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
             if (options.customButtons) {
@@ -108948,11 +108986,20 @@ var BABYLON;
                 var hmdBtn = document.createElement("button");
                 hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
                 hmdBtn.innerText = "HMD";
-                this._buttons.push({ element: hmdBtn, initializationOptions: { immersive: true } });
+                this._buttons.push(new WebXREnterExitUIButton(hmdBtn, { immersive: true, outputContext: options.outputCanvasContext }));
+                this._buttons[this._buttons.length - 1].update = function (activeButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "HMD";
+                };
                 var windowBtn = document.createElement("button");
                 windowBtn.style.cssText = hmdBtn.style.cssText;
                 windowBtn.innerText = "Window";
-                this._buttons.push({ element: windowBtn, initializationOptions: { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext } });
+                this._buttons.push(new WebXREnterExitUIButton(windowBtn, { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext }));
+                this._buttons[this._buttons.length - 1].update = function (activeButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "Window";
+                };
+                this._updateButtons(null);
             }
             var renderCanvas = scene.getEngine().getRenderingCanvas();
             if (renderCanvas && renderCanvas.parentNode) {
@@ -108973,7 +109020,12 @@ var BABYLON;
             var _this = this;
             var ui = new WebXREnterExitUI(scene, options);
             var supportedPromises = ui._buttons.map(function (btn) {
-                return helper.supportsSession(btn.initializationOptions);
+                return helper.supportsSessionAsync(btn.initializationOptions);
+            });
+            helper.onStateChangedObservable.add(function (state) {
+                if (state == BABYLON.WebXRState.NOT_IN_XR) {
+                    ui._updateButtons(null);
+                }
             });
             return Promise.all(supportedPromises).then(function (results) {
                 results.forEach(function (supported, i) {
@@ -108984,13 +109036,15 @@ var BABYLON;
                                 switch (_a.label) {
                                     case 0:
                                         if (!(helper.state == BABYLON.WebXRState.IN_XR)) return [3 /*break*/, 2];
-                                        return [4 /*yield*/, helper.exitXR()];
+                                        ui._updateButtons(null);
+                                        return [4 /*yield*/, helper.exitXRAsync()];
                                     case 1:
                                         _a.sent();
                                         return [2 /*return*/];
                                     case 2:
                                         if (!(helper.state == BABYLON.WebXRState.NOT_IN_XR)) return [3 /*break*/, 4];
-                                        return [4 /*yield*/, helper.enterXR(ui._buttons[i].initializationOptions, "eye-level")];
+                                        ui._updateButtons(ui._buttons[i]);
+                                        return [4 /*yield*/, helper.enterXRAsync(ui._buttons[i].initializationOptions, "eye-level")];
                                     case 3:
                                         _a.sent();
                                         _a.label = 4;
@@ -109002,6 +109056,13 @@ var BABYLON;
                 });
             });
         };
+        WebXREnterExitUI.prototype._updateButtons = function (activeButton) {
+            var _this = this;
+            this._activeButton = activeButton;
+            this._buttons.forEach(function (b) {
+                b.update(_this._activeButton);
+            });
+        };
         /**
          * Disposes of the object
          */
@@ -109038,7 +109099,7 @@ var BABYLON;
             this.canvasContext = null;
             if (!canvas) {
                 canvas = document.createElement('canvas');
-                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #000000;";
             }
             this._setManagedOutputCanvas(canvas);
             helper.onStateChangedObservable.add(function (stateInfo) {
@@ -109086,6 +109147,104 @@ var BABYLON;
 
 //# sourceMappingURL=babylon.webXRManagedOutputCanvas.js.map
 
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Represents an XR input
+     */
+    var WebXRController = /** @class */ (function () {
+        /**
+         * Creates the controller
+         * @see https://doc.babylonjs.com/how_to/webxr
+         * @param scene the scene which the controller should be associated to
+         */
+        function WebXRController(scene) {
+            this.pointer = new BABYLON.AbstractMesh("controllerPointer", scene);
+        }
+        /**
+         * Disposes of the object
+         */
+        WebXRController.prototype.dispose = function () {
+            if (this.grip) {
+                this.grip.dispose();
+            }
+            this.pointer.dispose();
+        };
+        return WebXRController;
+    }());
+    BABYLON.WebXRController = WebXRController;
+    /**
+     * XR input used to track XR inputs such as controllers/rays
+     */
+    var WebXRInput = /** @class */ (function () {
+        /**
+         * Initializes the WebXRInput
+         * @param helper experience helper which the input should be created for
+         */
+        function WebXRInput(helper) {
+            var _this = this;
+            this.helper = helper;
+            /**
+             * XR controllers being tracked
+             */
+            this.controllers = [];
+            this._tmpMatrix = new BABYLON.Matrix();
+            this._frameObserver = helper._sessionManager.onXRFrameObservable.add(function () {
+                if (!helper._sessionManager._currentXRFrame || !helper._sessionManager._currentXRFrame.getDevicePose) {
+                    return false;
+                }
+                var xrFrame = helper._sessionManager._currentXRFrame;
+                var inputSources = helper._sessionManager._xrSession.getInputSources();
+                inputSources.forEach(function (input, i) {
+                    var inputPose = xrFrame.getInputPose(input, helper._sessionManager._frameOfReference);
+                    if (inputPose) {
+                        if (_this.controllers.length <= i) {
+                            _this.controllers.push(new WebXRController(helper.container.getScene()));
+                        }
+                        var controller = _this.controllers[i];
+                        // Manage the grip if it exists
+                        if (inputPose.gripMatrix) {
+                            if (!controller.grip) {
+                                controller.grip = new BABYLON.AbstractMesh("controllerGrip", helper.container.getScene());
+                            }
+                            BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.gripMatrix, 0, 1, _this._tmpMatrix);
+                            if (!controller.grip.getScene().useRightHandedSystem) {
+                                _this._tmpMatrix.toggleModelMatrixHandInPlace();
+                            }
+                            if (!controller.grip.rotationQuaternion) {
+                                controller.grip.rotationQuaternion = new BABYLON.Quaternion();
+                            }
+                            _this._tmpMatrix.decompose(controller.grip.scaling, controller.grip.rotationQuaternion, controller.grip.position);
+                        }
+                        // Manager pointer of controller
+                        BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.targetRay.transformMatrix, 0, 1, _this._tmpMatrix);
+                        if (!controller.pointer.getScene().useRightHandedSystem) {
+                            _this._tmpMatrix.toggleModelMatrixHandInPlace();
+                        }
+                        if (!controller.pointer.rotationQuaternion) {
+                            controller.pointer.rotationQuaternion = new BABYLON.Quaternion();
+                        }
+                        _this._tmpMatrix.decompose(controller.pointer.scaling, controller.pointer.rotationQuaternion, controller.pointer.position);
+                    }
+                });
+            });
+        }
+        /**
+         * Disposes of the object
+         */
+        WebXRInput.prototype.dispose = function () {
+            this.controllers.forEach(function (c) {
+                c.dispose();
+            });
+            this.helper._sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        };
+        return WebXRInput;
+    }());
+    BABYLON.WebXRInput = WebXRInput;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.webXRInput.js.map
+
 // Mainly based on these 2 articles :
 // Creating an universal virtual touch joystick working for all Touch models thanks to Hand.JS : http://blogs.msdn.com/b/davrous/archive/2013/02/22/creating-an-universal-virtual-touch-joystick-working-for-all-touch-models-thanks-to-hand-js.aspx
 // & on Seb Lee-Delisle original work: http://seb.ly/2011/04/multi-touch-game-controller-in-javascripthtml5-for-ipad/
@@ -119986,7 +120145,9 @@ var BABYLON;
         var _this = this;
         return BABYLON.WebXRExperienceHelper.CreateAsync(this).then(function (helper) {
             var outputCanvas = new BABYLON.WebXRManagedOutputCanvas(helper);
-            return BABYLON.WebXREnterExitUI.CreateAsync(_this, helper, { outputCanvasContext: outputCanvas.canvasContext }).then(function (ui) {
+            return BABYLON.WebXREnterExitUI.CreateAsync(_this, helper, { outputCanvasContext: outputCanvas.canvasContext })
+                .then(function (ui) {
+                new BABYLON.WebXRInput(helper);
                 return helper;
             });
         });

+ 181 - 20
dist/preview release/babylon.no-module.max.js

@@ -77004,7 +77004,7 @@ var BABYLON;
             var scene = this._scene;
             var engine = scene.getEngine();
             var material = subMesh.getMaterial();
-            if (!material) {
+            if (!material || subMesh.verticesCount === 0) {
                 return;
             }
             // Culling
@@ -77042,6 +77042,9 @@ var BABYLON;
                     var skeleton = mesh.skeleton;
                     if (skeleton.isUsingTextureForMatrices) {
                         var boneTexture = skeleton.getTransformMatrixTexture();
+                        if (!boneTexture) {
+                            return;
+                        }
                         this._effect.setTexture("boneSampler", boneTexture);
                         this._effect.setFloat("boneTextureWidth", 4.0 * (skeleton.bones.length + 1));
                     }
@@ -108576,7 +108579,7 @@ var BABYLON;
          * After initialization enterXR can be called to start an XR session
          * @returns Promise which resolves after it is initialized
          */
-        WebXRSessionManager.prototype.initialize = function () {
+        WebXRSessionManager.prototype.initializeAsync = function () {
             var _this = this;
             // Check if the browser supports webXR
             this._xrNavigator = navigator;
@@ -108595,7 +108598,7 @@ var BABYLON;
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
          */
-        WebXRSessionManager.prototype.enterXR = function (sessionCreationOptions, frameOfReferenceType) {
+        WebXRSessionManager.prototype.enterXRAsync = function (sessionCreationOptions, frameOfReferenceType) {
             var _this = this;
             // initialize session
             return this._xrDevice.requestSession(sessionCreationOptions).then(function (session) {
@@ -108636,7 +108639,7 @@ var BABYLON;
          * Stops the xrSession and restores the renderloop
          * @returns Promise which resolves after it exits XR
          */
-        WebXRSessionManager.prototype.exitXR = function () {
+        WebXRSessionManager.prototype.exitXRAsync = function () {
             return this._xrSession.end();
         };
         /**
@@ -108644,7 +108647,7 @@ var BABYLON;
          * @param ray ray to cast into the environment
          * @returns Promise which resolves with a collision point in the environment if it exists
          */
-        WebXRSessionManager.prototype.environmentPointHitTest = function (ray) {
+        WebXRSessionManager.prototype.environmentPointHitTestAsync = function (ray) {
             var _this = this;
             return new Promise(function (res, rej) {
                 // Compute left handed inputs to request hit test
@@ -108678,7 +108681,7 @@ var BABYLON;
          * @param options creation options to check if they are supported
          * @returns true if supported
          */
-        WebXRSessionManager.prototype.supportsSession = function (options) {
+        WebXRSessionManager.prototype.supportsSessionAsync = function (options) {
             return this._xrDevice.supportsSession(options).then(function () {
                 return true;
             }).catch(function (e) {
@@ -108777,7 +108780,7 @@ var BABYLON;
          */
         WebXRExperienceHelper.CreateAsync = function (scene) {
             var helper = new WebXRExperienceHelper(scene);
-            return helper._sessionManager.initialize().then(function () {
+            return helper._sessionManager.initializeAsync().then(function () {
                 helper._supported = true;
                 return helper;
             }).catch(function () {
@@ -108788,9 +108791,9 @@ var BABYLON;
          * Exits XR mode and returns the scene to its original state
          * @returns promise that resolves after xr mode has exited
          */
-        WebXRExperienceHelper.prototype.exitXR = function () {
+        WebXRExperienceHelper.prototype.exitXRAsync = function () {
             this._setState(WebXRState.EXITING_XR);
-            return this._sessionManager.exitXR();
+            return this._sessionManager.exitXRAsync();
         };
         /**
          * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
@@ -108798,10 +108801,10 @@ var BABYLON;
          * @param frameOfReference frame of reference of the XR session
          * @returns promise that resolves after xr mode has entered
          */
-        WebXRExperienceHelper.prototype.enterXR = function (sessionCreationOptions, frameOfReference) {
+        WebXRExperienceHelper.prototype.enterXRAsync = function (sessionCreationOptions, frameOfReference) {
             var _this = this;
             this._setState(WebXRState.ENTERING_XR);
-            return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(function () {
+            return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(function () {
                 // Cache pre xr scene settings
                 _this._originalSceneAutoClear = _this.scene.autoClear;
                 _this._nonVRCamera = _this.scene.activeCamera;
@@ -108826,15 +108829,23 @@ var BABYLON;
             });
         };
         /**
+         * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
+         * @param ray ray to cast into the environment
+         * @returns Promise which resolves with a collision point in the environment if it exists
+         */
+        WebXRExperienceHelper.prototype.environmentPointHitTestAsync = function (ray) {
+            return this._sessionManager.environmentPointHitTestAsync(ray);
+        };
+        /**
          * Checks if the creation options are supported by the xr session
          * @param options creation options
          * @returns true if supported
          */
-        WebXRExperienceHelper.prototype.supportsSession = function (options) {
+        WebXRExperienceHelper.prototype.supportsSessionAsync = function (options) {
             if (!this._supported) {
                 return Promise.resolve(false);
             }
-            return this._sessionManager.supportsSession(options);
+            return this._sessionManager.supportsSessionAsync(options);
         };
         /**
          * Disposes of the experience helper
@@ -108890,6 +108901,32 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
 var BABYLON;
 (function (BABYLON) {
     /**
+     * Button which can be used to enter a different mode of XR
+     */
+    var WebXREnterExitUIButton = /** @class */ (function () {
+        /**
+         * Creates a WebXREnterExitUIButton
+         * @param element button element
+         * @param initializationOptions XR initialization options for the button
+         */
+        function WebXREnterExitUIButton(
+        /** button element */
+        element, 
+        /** XR initialization options for the button */
+        initializationOptions) {
+            this.element = element;
+            this.initializationOptions = initializationOptions;
+        }
+        /**
+         * Overwritable function which can be used to update the button's visuals when the state changes
+         * @param activeButton the current active button in the UI
+         */
+        WebXREnterExitUIButton.prototype.update = function (activeButton) {
+        };
+        return WebXREnterExitUIButton;
+    }());
+    BABYLON.WebXREnterExitUIButton = WebXREnterExitUIButton;
+    /**
      * Options to create the webXR UI
      */
     var WebXREnterExitUIOptions = /** @class */ (function () {
@@ -108906,6 +108943,7 @@ var BABYLON;
             var _this = this;
             this.scene = scene;
             this._buttons = [];
+            this._activeButton = null;
             this._overlay = document.createElement("div");
             this._overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
             if (options.customButtons) {
@@ -108915,11 +108953,20 @@ var BABYLON;
                 var hmdBtn = document.createElement("button");
                 hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
                 hmdBtn.innerText = "HMD";
-                this._buttons.push({ element: hmdBtn, initializationOptions: { immersive: true } });
+                this._buttons.push(new WebXREnterExitUIButton(hmdBtn, { immersive: true, outputContext: options.outputCanvasContext }));
+                this._buttons[this._buttons.length - 1].update = function (activeButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "HMD";
+                };
                 var windowBtn = document.createElement("button");
                 windowBtn.style.cssText = hmdBtn.style.cssText;
                 windowBtn.innerText = "Window";
-                this._buttons.push({ element: windowBtn, initializationOptions: { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext } });
+                this._buttons.push(new WebXREnterExitUIButton(windowBtn, { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext }));
+                this._buttons[this._buttons.length - 1].update = function (activeButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "Window";
+                };
+                this._updateButtons(null);
             }
             var renderCanvas = scene.getEngine().getRenderingCanvas();
             if (renderCanvas && renderCanvas.parentNode) {
@@ -108940,7 +108987,12 @@ var BABYLON;
             var _this = this;
             var ui = new WebXREnterExitUI(scene, options);
             var supportedPromises = ui._buttons.map(function (btn) {
-                return helper.supportsSession(btn.initializationOptions);
+                return helper.supportsSessionAsync(btn.initializationOptions);
+            });
+            helper.onStateChangedObservable.add(function (state) {
+                if (state == BABYLON.WebXRState.NOT_IN_XR) {
+                    ui._updateButtons(null);
+                }
             });
             return Promise.all(supportedPromises).then(function (results) {
                 results.forEach(function (supported, i) {
@@ -108951,13 +109003,15 @@ var BABYLON;
                                 switch (_a.label) {
                                     case 0:
                                         if (!(helper.state == BABYLON.WebXRState.IN_XR)) return [3 /*break*/, 2];
-                                        return [4 /*yield*/, helper.exitXR()];
+                                        ui._updateButtons(null);
+                                        return [4 /*yield*/, helper.exitXRAsync()];
                                     case 1:
                                         _a.sent();
                                         return [2 /*return*/];
                                     case 2:
                                         if (!(helper.state == BABYLON.WebXRState.NOT_IN_XR)) return [3 /*break*/, 4];
-                                        return [4 /*yield*/, helper.enterXR(ui._buttons[i].initializationOptions, "eye-level")];
+                                        ui._updateButtons(ui._buttons[i]);
+                                        return [4 /*yield*/, helper.enterXRAsync(ui._buttons[i].initializationOptions, "eye-level")];
                                     case 3:
                                         _a.sent();
                                         _a.label = 4;
@@ -108969,6 +109023,13 @@ var BABYLON;
                 });
             });
         };
+        WebXREnterExitUI.prototype._updateButtons = function (activeButton) {
+            var _this = this;
+            this._activeButton = activeButton;
+            this._buttons.forEach(function (b) {
+                b.update(_this._activeButton);
+            });
+        };
         /**
          * Disposes of the object
          */
@@ -109005,7 +109066,7 @@ var BABYLON;
             this.canvasContext = null;
             if (!canvas) {
                 canvas = document.createElement('canvas');
-                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #000000;";
             }
             this._setManagedOutputCanvas(canvas);
             helper.onStateChangedObservable.add(function (stateInfo) {
@@ -109053,6 +109114,104 @@ var BABYLON;
 
 //# sourceMappingURL=babylon.webXRManagedOutputCanvas.js.map
 
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Represents an XR input
+     */
+    var WebXRController = /** @class */ (function () {
+        /**
+         * Creates the controller
+         * @see https://doc.babylonjs.com/how_to/webxr
+         * @param scene the scene which the controller should be associated to
+         */
+        function WebXRController(scene) {
+            this.pointer = new BABYLON.AbstractMesh("controllerPointer", scene);
+        }
+        /**
+         * Disposes of the object
+         */
+        WebXRController.prototype.dispose = function () {
+            if (this.grip) {
+                this.grip.dispose();
+            }
+            this.pointer.dispose();
+        };
+        return WebXRController;
+    }());
+    BABYLON.WebXRController = WebXRController;
+    /**
+     * XR input used to track XR inputs such as controllers/rays
+     */
+    var WebXRInput = /** @class */ (function () {
+        /**
+         * Initializes the WebXRInput
+         * @param helper experience helper which the input should be created for
+         */
+        function WebXRInput(helper) {
+            var _this = this;
+            this.helper = helper;
+            /**
+             * XR controllers being tracked
+             */
+            this.controllers = [];
+            this._tmpMatrix = new BABYLON.Matrix();
+            this._frameObserver = helper._sessionManager.onXRFrameObservable.add(function () {
+                if (!helper._sessionManager._currentXRFrame || !helper._sessionManager._currentXRFrame.getDevicePose) {
+                    return false;
+                }
+                var xrFrame = helper._sessionManager._currentXRFrame;
+                var inputSources = helper._sessionManager._xrSession.getInputSources();
+                inputSources.forEach(function (input, i) {
+                    var inputPose = xrFrame.getInputPose(input, helper._sessionManager._frameOfReference);
+                    if (inputPose) {
+                        if (_this.controllers.length <= i) {
+                            _this.controllers.push(new WebXRController(helper.container.getScene()));
+                        }
+                        var controller = _this.controllers[i];
+                        // Manage the grip if it exists
+                        if (inputPose.gripMatrix) {
+                            if (!controller.grip) {
+                                controller.grip = new BABYLON.AbstractMesh("controllerGrip", helper.container.getScene());
+                            }
+                            BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.gripMatrix, 0, 1, _this._tmpMatrix);
+                            if (!controller.grip.getScene().useRightHandedSystem) {
+                                _this._tmpMatrix.toggleModelMatrixHandInPlace();
+                            }
+                            if (!controller.grip.rotationQuaternion) {
+                                controller.grip.rotationQuaternion = new BABYLON.Quaternion();
+                            }
+                            _this._tmpMatrix.decompose(controller.grip.scaling, controller.grip.rotationQuaternion, controller.grip.position);
+                        }
+                        // Manager pointer of controller
+                        BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.targetRay.transformMatrix, 0, 1, _this._tmpMatrix);
+                        if (!controller.pointer.getScene().useRightHandedSystem) {
+                            _this._tmpMatrix.toggleModelMatrixHandInPlace();
+                        }
+                        if (!controller.pointer.rotationQuaternion) {
+                            controller.pointer.rotationQuaternion = new BABYLON.Quaternion();
+                        }
+                        _this._tmpMatrix.decompose(controller.pointer.scaling, controller.pointer.rotationQuaternion, controller.pointer.position);
+                    }
+                });
+            });
+        }
+        /**
+         * Disposes of the object
+         */
+        WebXRInput.prototype.dispose = function () {
+            this.controllers.forEach(function (c) {
+                c.dispose();
+            });
+            this.helper._sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        };
+        return WebXRInput;
+    }());
+    BABYLON.WebXRInput = WebXRInput;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.webXRInput.js.map
+
 // Mainly based on these 2 articles :
 // Creating an universal virtual touch joystick working for all Touch models thanks to Hand.JS : http://blogs.msdn.com/b/davrous/archive/2013/02/22/creating-an-universal-virtual-touch-joystick-working-for-all-touch-models-thanks-to-hand-js.aspx
 // & on Seb Lee-Delisle original work: http://seb.ly/2011/04/multi-touch-game-controller-in-javascripthtml5-for-ipad/
@@ -119953,7 +120112,9 @@ var BABYLON;
         var _this = this;
         return BABYLON.WebXRExperienceHelper.CreateAsync(this).then(function (helper) {
             var outputCanvas = new BABYLON.WebXRManagedOutputCanvas(helper);
-            return BABYLON.WebXREnterExitUI.CreateAsync(_this, helper, { outputCanvasContext: outputCanvas.canvasContext }).then(function (ui) {
+            return BABYLON.WebXREnterExitUI.CreateAsync(_this, helper, { outputCanvasContext: outputCanvas.canvasContext })
+                .then(function (ui) {
+                new BABYLON.WebXRInput(helper);
                 return helper;
             });
         });

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.worker.js


File diff suppressed because it is too large
+ 183 - 22
dist/preview release/es6.js


+ 4 - 2
dist/preview release/gui/babylon.gui.d.ts

@@ -2070,6 +2070,7 @@ declare module BABYLON.GUI {
             name?: string | undefined;
             protected _thumbWidth: ValueAndUnit;
             protected _barOffset: ValueAndUnit;
+            protected _displayThumb: boolean;
             protected _effectiveBarOffset: number;
             protected _renderLeft: number;
             protected _renderTop: number;
@@ -2080,6 +2081,8 @@ declare module BABYLON.GUI {
             protected _effectiveThumbThickness: number;
             /** BABYLON.Observable raised when the sldier value changes */
             onValueChangedObservable: BABYLON.Observable<number>;
+            /** Gets or sets a boolean indicating if the thumb must be rendered */
+            displayThumb: boolean;
             /** Gets or sets main bar offset (ie. the margin applied to the value bar) */
             barOffset: string | number;
             /** Gets main bar offset in pixels*/
@@ -2118,8 +2121,6 @@ declare module BABYLON.GUI {
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
-            /** Gets or sets a boolean indicating if the thumb must be rendered */
-            displayThumb: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -2141,6 +2142,7 @@ declare module BABYLON.GUI {
         */
     export class ImageBasedSlider extends BaseSlider {
             name?: string | undefined;
+            displayThumb: boolean;
             /**
                 * Gets or sets the image used to render the background
                 */

+ 46 - 26
dist/preview release/gui/babylon.gui.js

@@ -890,6 +890,7 @@ var BaseSlider = /** @class */ (function (_super) {
         _this._isVertical = false;
         _this._barOffset = new valueAndUnit_1.ValueAndUnit(5, valueAndUnit_1.ValueAndUnit.UNITMODE_PIXEL, false);
         _this._isThumbClamped = false;
+        _this._displayThumb = true;
         // Shared rendering info
         _this._effectiveBarOffset = 0;
         /** Observable raised when the sldier value changes */
@@ -899,6 +900,21 @@ var BaseSlider = /** @class */ (function (_super) {
         _this.isPointerBlocker = true;
         return _this;
     }
+    Object.defineProperty(BaseSlider.prototype, "displayThumb", {
+        /** Gets or sets a boolean indicating if the thumb must be rendered */
+        get: function () {
+            return this._displayThumb;
+        },
+        set: function (value) {
+            if (this._displayThumb === value) {
+                return;
+            }
+            this._displayThumb = value;
+            this._markAsDirty();
+        },
+        enumerable: true,
+        configurable: true
+    });
     Object.defineProperty(BaseSlider.prototype, "barOffset", {
         /** Gets or sets main bar offset (ie. the margin applied to the value bar) */
         get: function () {
@@ -1066,7 +1082,9 @@ var BaseSlider = /** @class */ (function (_super) {
         this._backgroundBoxLength = Math.max(this._currentMeasure.width, this._currentMeasure.height);
         this._backgroundBoxThickness = Math.min(this._currentMeasure.width, this._currentMeasure.height);
         this._effectiveThumbThickness = this._getThumbThickness(type);
-        this._backgroundBoxLength -= this._effectiveThumbThickness;
+        if (this.displayThumb) {
+            this._backgroundBoxLength -= this._effectiveThumbThickness;
+        }
         //throw error when height is less than width for vertical slider
         if ((this.isVertical && this._currentMeasure.height < this._currentMeasure.width)) {
             console.error("Height should be greater than width");
@@ -1081,7 +1099,7 @@ var BaseSlider = /** @class */ (function (_super) {
         this._backgroundBoxThickness -= (this._effectiveBarOffset * 2);
         if (this.isVertical) {
             this._renderLeft += this._effectiveBarOffset;
-            if (!this.isThumbClamped) {
+            if (!this.isThumbClamped && this.displayThumb) {
                 this._renderTop += (this._effectiveThumbThickness / 2);
             }
             this._renderHeight = this._backgroundBoxLength;
@@ -1089,7 +1107,7 @@ var BaseSlider = /** @class */ (function (_super) {
         }
         else {
             this._renderTop += this._effectiveBarOffset;
-            if (!this.isThumbClamped) {
+            if (!this.isThumbClamped && this.displayThumb) {
                 this._renderLeft += (this._effectiveThumbThickness / 2);
             }
             this._renderHeight = this._backgroundBoxThickness;
@@ -4835,6 +4853,20 @@ var ImageBasedSlider = /** @class */ (function (_super) {
         _this._tempMeasure = new measure_1.Measure(0, 0, 0, 0);
         return _this;
     }
+    Object.defineProperty(ImageBasedSlider.prototype, "displayThumb", {
+        get: function () {
+            return this._displayThumb && this.thumbImage != null;
+        },
+        set: function (value) {
+            if (this._displayThumb === value) {
+                return;
+            }
+            this._displayThumb = value;
+            this._markAsDirty();
+        },
+        enumerable: true,
+        configurable: true
+    });
     Object.defineProperty(ImageBasedSlider.prototype, "backgroundImage", {
         /**
          * Gets or sets the image used to render the background
@@ -4914,7 +4946,7 @@ var ImageBasedSlider = /** @class */ (function (_super) {
             // Background
             if (this._backgroundImage) {
                 this._tempMeasure.copyFromFloats(left, top, width, height);
-                if (this.isThumbClamped) {
+                if (this.isThumbClamped && this.displayThumb) {
                     if (this.isVertical) {
                         this._tempMeasure.height += this._effectiveThumbThickness;
                     }
@@ -4927,21 +4959,25 @@ var ImageBasedSlider = /** @class */ (function (_super) {
             // Bar
             if (this._valueBarImage) {
                 if (this.isVertical) {
-                    this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
-                    if (this.isThumbClamped) {
-                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, this._currentMeasure.height - thumbPosition);
+                    if (this.isThumbClamped && this.displayThumb) {
+                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
                     }
                     else {
                         this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
                     }
                 }
                 else {
-                    this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
+                    if (this.isThumbClamped && this.displayThumb) {
+                        this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
+                    }
+                    else {
+                        this._tempMeasure.copyFromFloats(left, top, thumbPosition, height);
+                    }
                 }
                 this._valueBarImage._draw(this._tempMeasure, context);
             }
             // Thumb
-            if (this._thumbImage) {
+            if (this.displayThumb) {
                 if (this.isVertical) {
                     this._tempMeasure.copyFromFloats(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
                 }
@@ -7204,24 +7240,8 @@ var Slider = /** @class */ (function (_super) {
         _this._background = "black";
         _this._borderColor = "white";
         _this._isThumbCircle = false;
-        _this._displayThumb = true;
         return _this;
     }
-    Object.defineProperty(Slider.prototype, "displayThumb", {
-        /** Gets or sets a boolean indicating if the thumb must be rendered */
-        get: function () {
-            return this._displayThumb;
-        },
-        set: function (value) {
-            if (this._displayThumb === value) {
-                return;
-            }
-            this._displayThumb = value;
-            this._markAsDirty();
-        },
-        enumerable: true,
-        configurable: true
-    });
     Object.defineProperty(Slider.prototype, "borderColor", {
         /** Gets or sets border color */
         get: function () {
@@ -7348,7 +7368,7 @@ var Slider = /** @class */ (function (_super) {
                         context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
                     }
                     else {
-                        context.fillRect(left, top + thumbPosition, width, this._currentMeasure.height - thumbPosition);
+                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
                     }
                 }
                 else {

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js.map


+ 8 - 4
dist/preview release/gui/babylon.gui.module.d.ts

@@ -2246,6 +2246,7 @@ declare module 'babylonjs-gui/2D/controls/baseSlider' {
             name?: string | undefined;
             protected _thumbWidth: ValueAndUnit;
             protected _barOffset: ValueAndUnit;
+            protected _displayThumb: boolean;
             protected _effectiveBarOffset: number;
             protected _renderLeft: number;
             protected _renderTop: number;
@@ -2256,6 +2257,8 @@ declare module 'babylonjs-gui/2D/controls/baseSlider' {
             protected _effectiveThumbThickness: number;
             /** Observable raised when the sldier value changes */
             onValueChangedObservable: Observable<number>;
+            /** Gets or sets a boolean indicating if the thumb must be rendered */
+            displayThumb: boolean;
             /** Gets or sets main bar offset (ie. the margin applied to the value bar) */
             barOffset: string | number;
             /** Gets main bar offset in pixels*/
@@ -2297,8 +2300,6 @@ declare module 'babylonjs-gui/2D/controls/slider' {
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
-            /** Gets or sets a boolean indicating if the thumb must be rendered */
-            displayThumb: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -2324,6 +2325,7 @@ declare module 'babylonjs-gui/2D/controls/imageBasedSlider' {
         */
     export class ImageBasedSlider extends BaseSlider {
             name?: string | undefined;
+            displayThumb: boolean;
             /**
                 * Gets or sets the image used to render the background
                 */
@@ -4996,6 +4998,7 @@ declare module BABYLON.GUI {
             name?: string | undefined;
             protected _thumbWidth: ValueAndUnit;
             protected _barOffset: ValueAndUnit;
+            protected _displayThumb: boolean;
             protected _effectiveBarOffset: number;
             protected _renderLeft: number;
             protected _renderTop: number;
@@ -5006,6 +5009,8 @@ declare module BABYLON.GUI {
             protected _effectiveThumbThickness: number;
             /** BABYLON.Observable raised when the sldier value changes */
             onValueChangedObservable: BABYLON.Observable<number>;
+            /** Gets or sets a boolean indicating if the thumb must be rendered */
+            displayThumb: boolean;
             /** Gets or sets main bar offset (ie. the margin applied to the value bar) */
             barOffset: string | number;
             /** Gets main bar offset in pixels*/
@@ -5044,8 +5049,6 @@ declare module BABYLON.GUI {
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
-            /** Gets or sets a boolean indicating if the thumb must be rendered */
-            displayThumb: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -5067,6 +5070,7 @@ declare module BABYLON.GUI {
         */
     export class ImageBasedSlider extends BaseSlider {
             name?: string | undefined;
+            displayThumb: boolean;
             /**
                 * Gets or sets the image used to render the background
                 */

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.min.js.map


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/viewer/babylon.viewer.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -9,9 +9,12 @@
   - Add customAnimationFrameRequester to allow sessions to hook into engine's render loop ([TrevorDev](https://github.com/TrevorDev))
   - camera customDefaultRenderTarget to allow cameras to render to a custom render target (eg. xr framebuffer) instead of the canvas ([TrevorDev](https://github.com/TrevorDev))
   - webXR camera which can be updated by a webXRSession ([TrevorDev](https://github.com/TrevorDev))
-  - webXRSessionManager to bridge xrSession to babylon's engine/camera ([TrevorDev](https://github.com/TrevorDev))
+  - webXRSessionManager to bridge xrSession to babylon's camera/engine ([TrevorDev](https://github.com/TrevorDev))
   - webXRExperienceHelper to setup a default XR experience ([TrevorDev](https://github.com/TrevorDev))
   - WebXREnterExitUI and WebXRManagedOutputCanvas classes to configure the XR experience ([TrevorDev](https://github.com/TrevorDev))
+  - WebXRInput manage controllers for the XR experience ([TrevorDev](https://github.com/TrevorDev))
+- GUI:
+  - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
 
 ## Updates
 

+ 20 - 3
gui/src/2D/controls/baseSlider.ts

@@ -13,6 +13,7 @@ export class BaseSlider extends Control {
     private _isVertical = false;
     protected _barOffset = new ValueAndUnit(5, ValueAndUnit.UNITMODE_PIXEL, false);
     private _isThumbClamped = false;
+    protected _displayThumb = true;
 
     // Shared rendering info
     protected _effectiveBarOffset = 0;
@@ -27,6 +28,20 @@ export class BaseSlider extends Control {
     /** Observable raised when the sldier value changes */
     public onValueChangedObservable = new Observable<number>();
 
+    /** Gets or sets a boolean indicating if the thumb must be rendered */
+    public get displayThumb(): boolean {
+        return this._displayThumb;
+    }
+
+    public set displayThumb(value: boolean) {
+        if (this._displayThumb === value) {
+            return;
+        }
+
+        this._displayThumb = value;
+        this._markAsDirty();
+    }
+
     /** Gets or sets main bar offset (ie. the margin applied to the value bar) */
     public get barOffset(): string | number {
         return this._barOffset.toString(this._host);
@@ -200,7 +215,9 @@ export class BaseSlider extends Control {
         this._backgroundBoxThickness = Math.min(this._currentMeasure.width, this._currentMeasure.height);
         this._effectiveThumbThickness = this._getThumbThickness(type);
 
-        this._backgroundBoxLength -= this._effectiveThumbThickness;
+        if (this.displayThumb) {
+            this._backgroundBoxLength -= this._effectiveThumbThickness;
+        }
         //throw error when height is less than width for vertical slider
         if ((this.isVertical && this._currentMeasure.height < this._currentMeasure.width)) {
             console.error("Height should be greater than width");
@@ -217,7 +234,7 @@ export class BaseSlider extends Control {
 
         if (this.isVertical) {
             this._renderLeft += this._effectiveBarOffset;
-            if (!this.isThumbClamped) {
+            if (!this.isThumbClamped && this.displayThumb) {
                 this._renderTop += (this._effectiveThumbThickness / 2);
             }
 
@@ -227,7 +244,7 @@ export class BaseSlider extends Control {
         }
         else {
             this._renderTop += this._effectiveBarOffset;
-            if (!this.isThumbClamped) {
+            if (!this.isThumbClamped && this.displayThumb) {
                 this._renderLeft += (this._effectiveThumbThickness / 2);
             }
             this._renderHeight = this._backgroundBoxThickness;

+ 23 - 7
gui/src/2D/controls/imageBasedSlider.ts

@@ -12,6 +12,19 @@ export class ImageBasedSlider extends BaseSlider {
 
     private _tempMeasure = new Measure(0, 0, 0, 0);
 
+    public get displayThumb(): boolean {
+        return this._displayThumb && this.thumbImage != null;
+    }
+
+    public set displayThumb(value: boolean) {
+        if (this._displayThumb === value) {
+            return;
+        }
+
+        this._displayThumb = value;
+        this._markAsDirty();
+    }
+
     /**
      * Gets or sets the image used to render the background
      */
@@ -103,13 +116,12 @@ export class ImageBasedSlider extends BaseSlider {
             // Background
             if (this._backgroundImage) {
                 this._tempMeasure.copyFromFloats(left, top, width, height);
-                if (this.isThumbClamped) {
+                if (this.isThumbClamped && this.displayThumb) {
                     if (this.isVertical) {
                         this._tempMeasure.height += this._effectiveThumbThickness;
                     } else {
                         this._tempMeasure.width += this._effectiveThumbThickness;
                     }
-
                 }
                 this._backgroundImage._draw(this._tempMeasure, context);
             }
@@ -117,20 +129,24 @@ export class ImageBasedSlider extends BaseSlider {
             // Bar
             if (this._valueBarImage) {
                 if (this.isVertical) {
-                    this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
-                    if (this.isThumbClamped) {
-                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, this._currentMeasure.height - thumbPosition);
+                    if (this.isThumbClamped && this.displayThumb) {
+                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
                     } else {
                         this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
                     }
                 } else {
-                    this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
+                    if (this.isThumbClamped && this.displayThumb) {
+                        this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
+                    }
+                    else {
+                        this._tempMeasure.copyFromFloats(left, top, thumbPosition, height);
+                    }
                 }
                 this._valueBarImage._draw(this._tempMeasure, context);
             }
 
             // Thumb
-            if (this._thumbImage) {
+            if (this.displayThumb) {
                 if (this.isVertical) {
                     this._tempMeasure.copyFromFloats(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
                 } else {

+ 1 - 16
gui/src/2D/controls/slider.ts

@@ -8,21 +8,6 @@ export class Slider extends BaseSlider {
     private _background = "black";
     private _borderColor = "white";
     private _isThumbCircle = false;
-    private _displayThumb = true;
-
-    /** Gets or sets a boolean indicating if the thumb must be rendered */
-    public get displayThumb(): boolean {
-        return this._displayThumb;
-    }
-
-    public set displayThumb(value: boolean) {
-        if (this._displayThumb === value) {
-            return;
-        }
-
-        this._displayThumb = value;
-        this._markAsDirty();
-    }
 
     /** Gets or sets border color */
     public get borderColor(): string {
@@ -166,7 +151,7 @@ export class Slider extends BaseSlider {
                         context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
                     }
                     else {
-                        context.fillRect(left, top + thumbPosition, width, this._currentMeasure.height - thumbPosition);
+                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
                     }
                 }
                 else {

+ 55 - 7
src/Cameras/XR/babylon.webXREnterExitUI.ts

@@ -1,5 +1,28 @@
 module BABYLON {
     /**
+     * Button which can be used to enter a different mode of XR
+     */
+    export class WebXREnterExitUIButton {
+        /**
+         * Creates a WebXREnterExitUIButton
+         * @param element button element
+         * @param initializationOptions XR initialization options for the button
+         */
+        constructor(
+            /** button element */
+            public element: HTMLElement,
+            /** XR initialization options for the button */
+            public initializationOptions: XRSessionCreationOptions
+        ) {}
+        /**
+         * Overwritable function which can be used to update the button's visuals when the state changes
+         * @param activeButton the current active button in the UI
+         */
+        update(activeButton: Nullable<WebXREnterExitUIButton>) {
+        }
+    }
+
+    /**
      * Options to create the webXR UI
      */
     export class WebXREnterExitUIOptions {
@@ -11,14 +34,15 @@ module BABYLON {
         /**
          * User provided buttons to enable/disable WebXR. The system will provide default if not set
          */
-        customButtons?: Array<{ element: HTMLElement, initializationOptions: XRSessionCreationOptions }>;
+        customButtons?: Array<WebXREnterExitUIButton>;
     }
     /**
      * UI to allow the user to enter/exit XR mode
      */
     export class WebXREnterExitUI implements IDisposable {
         private _overlay: HTMLDivElement;
-        private _buttons: Array<{ element: HTMLElement, initializationOptions: XRSessionCreationOptions }> = [];
+        private _buttons: Array<WebXREnterExitUIButton> = [];
+        private _activeButton: Nullable<WebXREnterExitUIButton> = null;
         /**
          * Creates UI to allow the user to enter/exit XR mode
          * @param scene the scene to add the ui to
@@ -29,7 +53,12 @@ module BABYLON {
         public static CreateAsync(scene: BABYLON.Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions) {
             var ui = new WebXREnterExitUI(scene, options);
             var supportedPromises = ui._buttons.map((btn) => {
-                return helper.supportsSession(btn.initializationOptions);
+                return helper.supportsSessionAsync(btn.initializationOptions);
+            });
+            helper.onStateChangedObservable.add((state) => {
+                if (state == WebXRState.NOT_IN_XR) {
+                    ui._updateButtons(null);
+                }
             });
             return Promise.all(supportedPromises).then((results) => {
                 results.forEach((supported, i) => {
@@ -37,10 +66,12 @@ module BABYLON {
                         ui._overlay.appendChild(ui._buttons[i].element);
                         ui._buttons[i].element.onclick = async() => {
                             if (helper.state == BABYLON.WebXRState.IN_XR) {
-                                await helper.exitXR();
+                                ui._updateButtons(null);
+                                await helper.exitXRAsync();
                                 return;
                             } else if (helper.state == BABYLON.WebXRState.NOT_IN_XR) {
-                                await helper.enterXR(ui._buttons[i].initializationOptions, "eye-level");
+                                ui._updateButtons(ui._buttons[i]);
+                                await helper.enterXRAsync(ui._buttons[i].initializationOptions, "eye-level");
                             }
                         };
                     }
@@ -58,12 +89,21 @@ module BABYLON {
                 var hmdBtn = document.createElement("button");
                 hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
                 hmdBtn.innerText = "HMD";
-                this._buttons.push({ element: hmdBtn, initializationOptions: { immersive: true } });
+                this._buttons.push(new WebXREnterExitUIButton(hmdBtn, {immersive: true, outputContext: options.outputCanvasContext}));
+                this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "HMD";
+                };
 
                 var windowBtn = document.createElement("button");
                 windowBtn.style.cssText = hmdBtn.style.cssText;
                 windowBtn.innerText = "Window";
-                this._buttons.push({ element: windowBtn, initializationOptions: { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext } });
+                this._buttons.push(new WebXREnterExitUIButton(windowBtn, { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext }));
+                this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
+                    this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
+                    this.element.innerText = activeButton === this ? "EXIT" : "Window";
+                };
+                this._updateButtons(null);
             }
 
             var renderCanvas = scene.getEngine().getRenderingCanvas();
@@ -74,6 +114,14 @@ module BABYLON {
                 });
             }
         }
+
+        private _updateButtons(activeButton: Nullable<WebXREnterExitUIButton>) {
+            this._activeButton = activeButton;
+            this._buttons.forEach((b) => {
+                b.update(this._activeButton);
+            });
+        }
+
         /**
          * Disposes of the object
          */

+ 18 - 8
src/Cameras/XR/babylon.webXRExperienceHelper.ts

@@ -49,7 +49,8 @@ module BABYLON {
          */
         public onStateChangedObservable = new Observable<WebXRState>();
 
-        private _sessionManager: WebXRSessionManager;
+        /** @hidden */
+        public _sessionManager: WebXRSessionManager;
 
         private _nonVRCamera: Nullable<Camera> = null;
         private _originalSceneAutoClear = true;
@@ -63,7 +64,7 @@ module BABYLON {
          */
         public static CreateAsync(scene: BABYLON.Scene): Promise<WebXRExperienceHelper> {
             var helper = new WebXRExperienceHelper(scene);
-            return helper._sessionManager.initialize().then(() => {
+            return helper._sessionManager.initializeAsync().then(() => {
                 helper._supported = true;
                 return helper;
             }).catch(() => {
@@ -85,9 +86,9 @@ module BABYLON {
          * Exits XR mode and returns the scene to its original state
          * @returns promise that resolves after xr mode has exited
          */
-        public exitXR() {
+        public exitXRAsync() {
             this._setState(WebXRState.EXITING_XR);
-            return this._sessionManager.exitXR();
+            return this._sessionManager.exitXRAsync();
         }
 
         /**
@@ -96,10 +97,10 @@ module BABYLON {
          * @param frameOfReference frame of reference of the XR session
          * @returns promise that resolves after xr mode has entered
          */
-        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
+        public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
             this._setState(WebXRState.ENTERING_XR);
 
-            return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(() => {
+            return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(() => {
                 // Cache pre xr scene settings
                 this._originalSceneAutoClear = this.scene.autoClear;
                 this._nonVRCamera = this.scene.activeCamera;
@@ -130,15 +131,24 @@ module BABYLON {
         }
 
         /**
+         * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
+         * @param ray ray to cast into the environment
+         * @returns Promise which resolves with a collision point in the environment if it exists
+         */
+        public environmentPointHitTestAsync(ray: BABYLON.Ray): Promise<Nullable<Vector3>> {
+            return this._sessionManager.environmentPointHitTestAsync(ray);
+        }
+
+        /**
          * Checks if the creation options are supported by the xr session
          * @param options creation options
          * @returns true if supported
          */
-        public supportsSession(options: XRSessionCreationOptions) {
+        public supportsSessionAsync(options: XRSessionCreationOptions) {
             if (!this._supported) {
                 return Promise.resolve(false);
             }
-            return this._sessionManager.supportsSession(options);
+            return this._sessionManager.supportsSessionAsync(options);
         }
 
         /**

+ 104 - 0
src/Cameras/XR/babylon.webXRInput.ts

@@ -0,0 +1,104 @@
+module BABYLON {
+    /**
+     * Represents an XR input
+     */
+    export class WebXRController {
+        /**
+         * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
+         */
+        public grip?: BABYLON.AbstractMesh;
+        /**
+         * Pointer which can be used to select objects or attach a visible laser to
+         */
+        public pointer: BABYLON.AbstractMesh;
+
+        /**
+         * Creates the controller
+         * @see https://doc.babylonjs.com/how_to/webxr
+         * @param scene the scene which the controller should be associated to
+         */
+        constructor(scene: BABYLON.Scene) {
+            this.pointer = new BABYLON.AbstractMesh("controllerPointer", scene);
+        }
+        /**
+         * Disposes of the object
+         */
+        dispose() {
+            if (this.grip) {
+                this.grip.dispose();
+            }
+            this.pointer.dispose();
+        }
+    }
+
+    /**
+     * XR input used to track XR inputs such as controllers/rays
+     */
+    export class WebXRInput implements IDisposable {
+        /**
+         * XR controllers being tracked
+         */
+        public controllers: Array<WebXRController> = [];
+        private _tmpMatrix = new BABYLON.Matrix();
+        private _frameObserver: Nullable<Observer<any>>;
+
+        /**
+         * Initializes the WebXRInput
+         * @param helper experience helper which the input should be created for
+         */
+        public constructor(private helper: WebXRExperienceHelper) {
+            this._frameObserver = helper._sessionManager.onXRFrameObservable.add(() => {
+                if (!helper._sessionManager._currentXRFrame || !helper._sessionManager._currentXRFrame.getDevicePose) {
+                    return false;
+                }
+
+                var xrFrame = helper._sessionManager._currentXRFrame;
+                var inputSources = helper._sessionManager._xrSession.getInputSources();
+
+                inputSources.forEach((input, i) => {
+                    let inputPose = xrFrame.getInputPose(input, helper._sessionManager._frameOfReference);
+                    if (inputPose) {
+                        if (this.controllers.length <= i) {
+                            this.controllers.push(new WebXRController(helper.container.getScene()));
+                        }
+                        var controller = this.controllers[i];
+
+                        // Manage the grip if it exists
+                        if (inputPose.gripMatrix) {
+                            if (!controller.grip) {
+                                controller.grip = new BABYLON.AbstractMesh("controllerGrip", helper.container.getScene());
+                            }
+                            BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.gripMatrix, 0, 1, this._tmpMatrix);
+                            if (!controller.grip.getScene().useRightHandedSystem) {
+                                this._tmpMatrix.toggleModelMatrixHandInPlace();
+                            }
+                            if (!controller.grip.rotationQuaternion) {
+                                controller.grip.rotationQuaternion = new BABYLON.Quaternion();
+                            }
+                            this._tmpMatrix.decompose(controller.grip.scaling, controller.grip.rotationQuaternion, controller.grip.position);
+                        }
+
+                        // Manager pointer of controller
+                        BABYLON.Matrix.FromFloat32ArrayToRefScaled(inputPose.targetRay.transformMatrix, 0, 1, this._tmpMatrix);
+                        if (!controller.pointer.getScene().useRightHandedSystem) {
+                            this._tmpMatrix.toggleModelMatrixHandInPlace();
+                        }
+                        if (!controller.pointer.rotationQuaternion) {
+                            controller.pointer.rotationQuaternion = new BABYLON.Quaternion();
+                        }
+                        this._tmpMatrix.decompose(controller.pointer.scaling, controller.pointer.rotationQuaternion, controller.pointer.position);
+                    }
+                });
+            });
+        }
+        /**
+         * Disposes of the object
+         */
+        public dispose() {
+            this.controllers.forEach((c) => {
+                c.dispose();
+            });
+            this.helper._sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        }
+    }
+}

+ 1 - 1
src/Cameras/XR/babylon.webXRManagedOutputCanvas.ts

@@ -16,7 +16,7 @@ module BABYLON {
         public constructor(helper: WebXRExperienceHelper, canvas?: HTMLCanvasElement) {
             if (!canvas) {
                 canvas = document.createElement('canvas');
-                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #000000;";
             }
             this._setManagedOutputCanvas(canvas);
             helper.onStateChangedObservable.add((stateInfo) => {

+ 5 - 5
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -38,7 +38,7 @@ module BABYLON {
          * After initialization enterXR can be called to start an XR session
          * @returns Promise which resolves after it is initialized
          */
-        public initialize(): Promise<void> {
+        public initializeAsync(): Promise<void> {
              // Check if the browser supports webXR
             this._xrNavigator = navigator;
             if (!this._xrNavigator.xr) {
@@ -57,7 +57,7 @@ module BABYLON {
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
          */
-        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
+        public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
             // initialize session
             return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
                 this._xrSession = session;
@@ -103,7 +103,7 @@ module BABYLON {
          * Stops the xrSession and restores the renderloop
          * @returns Promise which resolves after it exits XR
          */
-        public exitXR() {
+        public exitXRAsync() {
             return this._xrSession.end();
         }
 
@@ -112,7 +112,7 @@ module BABYLON {
          * @param ray ray to cast into the environment
          * @returns Promise which resolves with a collision point in the environment if it exists
          */
-        public environmentPointHitTest(ray: BABYLON.Ray): Promise<Nullable<Vector3>> {
+        public environmentPointHitTestAsync(ray: BABYLON.Ray): Promise<Nullable<Vector3>> {
             return new Promise((res, rej) => {
                 // Compute left handed inputs to request hit test
                 var origin = new Float32Array([ray.origin.x, ray.origin.y, ray.origin.z]);
@@ -146,7 +146,7 @@ module BABYLON {
          * @param options creation options to check if they are supported
          * @returns true if supported
          */
-        public supportsSession(options: XRSessionCreationOptions) {
+        public supportsSessionAsync(options: XRSessionCreationOptions) {
             return this._xrDevice.supportsSession(options).then(() => {
                 return true;
             }).catch((e) => {

+ 3 - 1
src/Helpers/babylon.sceneHelpers.ts

@@ -184,7 +184,9 @@ module BABYLON {
     Scene.prototype.createDefaultXRExperienceAsync = function(): Promise<BABYLON.WebXRExperienceHelper> {
         return BABYLON.WebXRExperienceHelper.CreateAsync(this).then((helper) => {
             var outputCanvas = new BABYLON.WebXRManagedOutputCanvas(helper);
-            return BABYLON.WebXREnterExitUI.CreateAsync(this, helper, {outputCanvasContext: outputCanvas.canvasContext}).then((ui) => {
+            return BABYLON.WebXREnterExitUI.CreateAsync(this, helper, {outputCanvasContext: outputCanvas.canvasContext})
+            .then((ui) => {
+                new BABYLON.WebXRInput(helper);
                 return helper;
             });
         });

+ 6 - 1
src/Lights/Shadows/babylon.shadowGenerator.ts

@@ -865,7 +865,7 @@ module BABYLON {
             var engine = scene.getEngine();
             let material = subMesh.getMaterial();
 
-            if (!material) {
+            if (!material || subMesh.verticesCount === 0) {
                 return;
             }
 
@@ -912,6 +912,11 @@ module BABYLON {
 
                     if (skeleton.isUsingTextureForMatrices) {
                         const boneTexture = skeleton.getTransformMatrixTexture();
+
+                        if (!boneTexture) {
+                            return;
+                        }
+
                         this._effect.setTexture("boneSampler", boneTexture);
                         this._effect.setFloat("boneTextureWidth", 4.0 * (skeleton.bones.length + 1));
                     } else {

+ 2 - 0
src/babylon.mixins.ts

@@ -184,6 +184,7 @@ interface XRDevice {
     supportsSession(options: XRSessionCreationOptions): Promise<void>;
 }
 interface XRSession {
+    getInputSources(): Array<any>;
     baseLayer: XRWebGLLayer;
     requestFrameOfReference(type: string): Promise<void>;
     requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
@@ -206,6 +207,7 @@ interface XRView {
 }
 interface XRFrame {
     getDevicePose: Function;
+    getInputPose: Function;
     views: Array<XRView>;
     baseLayer: XRLayer;
 }

BIN
tests/validation/ReferenceImages/Sliders.png


+ 6 - 0
tests/validation/config.json

@@ -2,6 +2,12 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "Sliders",
+      "playgroundId": "#HATGQZ#3",
+      "referenceImage": "Sliders.png",
+      "excludeFromAutomaticTesting": true
+    },
+    {
       "title": "Pointers",
       "playgroundId": "#1GLEJK#5",
       "referenceImage": "pointers.png",