Bladeren bron

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 5 jaren geleden
bovenliggende
commit
c89bc83700
100 gewijzigde bestanden met toevoegingen van 1968 en 2116 verwijderingen
  1. 0 4
      Playground/full.html
  2. 3 2
      Playground/js/frame.js
  3. 2 2
      Viewer/src/labs/viewerLabs.ts
  4. 1 1
      Viewer/src/model/modelAnimation.ts
  5. 1 1
      Viewer/src/model/viewerModel.ts
  6. 4 1
      dist/preview release/what's new.md
  7. 8 5
      gui/src/2D/advancedDynamicTexture.ts
  8. 3 3
      gui/src/2D/controls/button.ts
  9. 1 1
      gui/src/2D/controls/checkbox.ts
  10. 2 1
      gui/src/2D/controls/colorpicker.ts
  11. 3 3
      gui/src/2D/controls/container.ts
  12. 26 4
      gui/src/2D/controls/control.ts
  13. 1 1
      gui/src/2D/controls/inputText.ts
  14. 1 1
      gui/src/2D/controls/line.ts
  15. 1 1
      gui/src/2D/controls/radioButton.ts
  16. 11 17
      gui/src/2D/controls/scrollViewers/scrollViewer.ts
  17. 1 1
      gui/src/2D/controls/sliders/baseSlider.ts
  18. 1 1
      gui/src/2D/controls/sliders/imageScrollBar.ts
  19. 1 1
      gui/src/2D/controls/sliders/scrollBar.ts
  20. 2 1
      gui/src/2D/math2D.ts
  21. 1 1
      gui/src/2D/measure.ts
  22. 1 1
      gui/src/2D/multiLinePoint.ts
  23. 2 1
      gui/src/3D/controls/button3D.ts
  24. 1 1
      gui/src/3D/controls/control3D.ts
  25. 2 1
      gui/src/3D/controls/holographicButton.ts
  26. 1 1
      gui/src/3D/controls/planePanel.ts
  27. 1 1
      gui/src/3D/controls/scatterPanel.ts
  28. 2 1
      gui/src/3D/controls/spherePanel.ts
  29. 1 1
      gui/src/3D/controls/stackPanel3D.ts
  30. 1 1
      gui/src/3D/gui3DManager.ts
  31. 2 1
      gui/src/3D/materials/fluentMaterial.ts
  32. 1 1
      gui/src/3D/vector3WithInfo.ts
  33. 5 5
      inspector/src/components/actionTabs/lines/color3LineComponent.tsx
  34. 1 1
      inspector/src/components/actionTabs/lines/color4LineComponent.tsx
  35. 1 1
      inspector/src/components/actionTabs/lines/quaternionLineComponent.tsx
  36. 1 1
      inspector/src/components/actionTabs/lines/vector2LineComponent.tsx
  37. 1 1
      inspector/src/components/actionTabs/lines/vector3LineComponent.tsx
  38. 1 1
      inspector/src/components/actionTabs/lines/vector4LineComponent.tsx
  39. 10 9
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx
  40. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/renderGridPropertyGridComponent.tsx
  41. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx
  42. 1 1
      loaders/src/OBJ/mtlFileLoader.ts
  43. 2 1
      loaders/src/OBJ/objFileLoader.ts
  44. 2 1
      loaders/src/glTF/1.0/glTFLoader.ts
  45. 2 1
      loaders/src/glTF/1.0/glTFLoaderUtils.ts
  46. 2 1
      loaders/src/glTF/1.0/glTFMaterialsCommonExtension.ts
  47. 1 1
      loaders/src/glTF/2.0/Extensions/EXT_lights_image_based.ts
  48. 2 1
      loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts
  49. 1 1
      loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts
  50. 1 1
      loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts
  51. 1 1
      loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts
  52. 2 1
      loaders/src/glTF/2.0/glTFLoader.ts
  53. 5 5
      nodeEditor/src/sharedComponents/color3LineComponent.tsx
  54. 6 6
      nodeEditor/src/sharedComponents/color4LineComponent.tsx
  55. 1 1
      nodeEditor/src/sharedComponents/matrixLineComponent.tsx
  56. 1 1
      nodeEditor/src/sharedComponents/vector2LineComponent.tsx
  57. 1 1
      nodeEditor/src/sharedComponents/vector3LineComponent.tsx
  58. 1 1
      nodeEditor/src/sharedComponents/vector4LineComponent.tsx
  59. 1 1
      package.json
  60. 1 1
      postProcessLibrary/src/digitalRain/digitalRainPostProcess.ts
  61. 1 1
      proceduralTexturesLibrary/src/brick/brickProceduralTexture.ts
  62. 1 1
      proceduralTexturesLibrary/src/cloud/cloudProceduralTexture.ts
  63. 2 1
      proceduralTexturesLibrary/src/fire/fireProceduralTexture.ts
  64. 1 1
      proceduralTexturesLibrary/src/grass/grassProceduralTexture.ts
  65. 1 1
      proceduralTexturesLibrary/src/marble/marbleProceduralTexture.ts
  66. 1 1
      proceduralTexturesLibrary/src/road/roadProceduralTexture.ts
  67. 1 1
      proceduralTexturesLibrary/src/wood/woodProceduralTexture.ts
  68. 1 1
      serializers/src/OBJ/objSerializer.ts
  69. 230 175
      serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts
  70. 1 1
      serializers/src/glTF/2.0/glTFAnimation.ts
  71. 17 13
      serializers/src/glTF/2.0/glTFExporter.ts
  72. 5 4
      serializers/src/glTF/2.0/glTFExporterExtension.ts
  73. 2 1
      serializers/src/glTF/2.0/glTFMaterialExporter.ts
  74. 1 1
      serializers/src/glTF/2.0/glTFUtilities.ts
  75. 1 1
      serializers/src/stl/stlSerializer.ts
  76. 19 19
      src/XR/features/WebXRAbstractFeature.ts
  77. 80 83
      src/XR/features/WebXRAnchorSystem.ts
  78. 19 20
      src/XR/features/WebXRBackgroundRemover.ts
  79. 134 142
      src/XR/features/WebXRControllerPhysics.ts
  80. 136 149
      src/XR/features/WebXRControllerPointerSelection.ts
  81. 201 213
      src/XR/features/WebXRControllerTeleportation.ts
  82. 68 70
      src/XR/features/WebXRHitTestLegacy.ts
  83. 27 28
      src/XR/features/WebXRPlaneDetector.ts
  84. 117 120
      src/XR/motionController/webXRAbstractMotionController.ts
  85. 58 58
      src/XR/motionController/webXRControllerComponent.ts
  86. 10 11
      src/XR/motionController/webXRGenericMotionController.ts
  87. 19 33
      src/XR/motionController/webXRHTCViveMotionController.ts
  88. 43 40
      src/XR/motionController/webXRMicrosoftMixedRealityController.ts
  89. 90 92
      src/XR/motionController/webXRMotionControllerManager.ts
  90. 35 194
      src/XR/motionController/webXROculusTouchMotionController.ts
  91. 24 17
      src/XR/motionController/webXRProfiledMotionController.ts
  92. 25 33
      src/XR/webXRDefaultExperience.ts
  93. 67 67
      src/XR/webXREnterExitUI.ts
  94. 50 56
      src/XR/webXRExperienceHelper.ts
  95. 105 107
      src/XR/webXRFeaturesManager.ts
  96. 1 1
      src/XR/webXRInput.ts
  97. 53 58
      src/XR/webXRInputSource.ts
  98. 40 43
      src/XR/webXRManagedOutputCanvas.ts
  99. 136 145
      src/XR/webXRSessionManager.ts
  100. 0 0
      tests/validation/ReferenceImages/glTFSerializerNegativeWorldMatrix.png

+ 0 - 4
Playground/full.html

@@ -4,10 +4,6 @@
     <head>
         <title>Babylon.js Playground</title>
         <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
-        <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
-        <meta name="msapplication-TileColor" content="#ffffff">
-        <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
-        <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
         <meta name="theme-color" content="#ffffff">
         <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
 

+ 3 - 2
Playground/js/frame.js

@@ -118,8 +118,9 @@ run = function () {
                 if (scene.activeCamera || scene.activeCameras.length > 0) {
                     scene.render();
                 }
-
-                fpsLabel.innerHTML = engine.getFps().toFixed() + " fps";
+                if (fpsLabel) {
+                    fpsLabel.innerHTML = engine.getFps().toFixed() + " fps";
+                }
             }.bind(this));
 
         } catch (e) {

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

@@ -1,10 +1,10 @@
 import { PBREnvironment, EnvironmentDeserializer } from "./environmentSerializer";
 import { Scene } from "babylonjs/scene";
-import { Vector3, Quaternion, Axis, Matrix, TmpVectors } from "babylonjs/Maths/math";
+import { Vector3, Quaternion, Matrix, TmpVectors } from "babylonjs/Maths/math.vector";
 import { SphericalPolynomial } from "babylonjs/Maths/sphericalPolynomial";
 import { ShadowLight } from "babylonjs/Lights/shadowLight";
 import { TextureUtils } from "./texture";
-
+import { Axis } from "babylonjs/Maths/math.axis";
 
 /**
  * The ViewerLabs class will hold functions that are not (!) backwards compatible.

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

@@ -1,4 +1,4 @@
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { AnimationGroup, Animatable } from "babylonjs/Animations/index";
 
 /**

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

@@ -8,7 +8,7 @@ import { SceneLoaderProgressEvent } from "babylonjs/Loading/sceneLoader";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 import { Animation, Animatable, CircleEase, BackEase, BounceEase, CubicEase, ElasticEase, ExponentialEase, PowerEase, QuadraticEase, QuarticEase, QuinticEase, SineEase } from "babylonjs/Animations/index";
 import { Nullable } from "babylonjs/types";
-import { Quaternion, Vector3 } from "babylonjs/Maths/math";
+import { Quaternion, Vector3 } from "babylonjs/Maths/math.vector";
 import { Tags } from "babylonjs/Misc/tags";
 import { Material } from "babylonjs/Materials/material";
 import { PBRMaterial } from "babylonjs/Materials/PBR/pbrMaterial";

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

@@ -212,7 +212,7 @@
 - It is now possible to force a certain profile type for the controllers ([#7348](https://github.com/BabylonJS/Babylon.js/issues/7375)) ([RaananW](https://github.com/RaananW/))
 - WebXR camera is initialized on the first frame, including copying transformation from native camera (except for in AR) ([#7389](https://github.com/BabylonJS/Babylon.js/issues/7389)) ([RaananW](https://github.com/RaananW/))
 - Selection has gaze mode (which can be forced) and touch-screen support ([#7395](https://github.com/BabylonJS/Babylon.js/issues/7395)) ([RaananW](https://github.com/RaananW/))
-- Laser pointers can be excluded from lighting influence so that they are always visible in both WebXR and WebVR ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
+- Laser pointers can be excluded from lighting influence so that they are always visible in WebXR / WebVR ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - Full support for the online motion controller repository ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - New XR feature - XR Controller physics impostor for motion controllers / XR Input sources ([RaananW](https://github.com/RaananW/))
 - Teleportation between different ground levels in WebXR is enabled ([RaananW](https://github.com/RaananW/))
@@ -234,6 +234,8 @@
 - Scroll Viewer extended to include the use of images in the scroll bars([JohnK](https://github.com/BabylonJSGuide/))
 - Added `ScrollViewer.freezeControls` property to speed up rendering ([Popov72](https://github.com/Popov72))
 - Added `ImageScrollBar.num90RotationInVerticalMode` property to let the user rotate the pictures when in vertical mode ([Popov72](https://github.com/Popov72))
+- Modified isPointerBlocker to block mouse wheel scroll events. ScrollViewer mouse scroll no longer dependent on scene. ([lockphase](https://github.com/lockphase/))
+
 
 ### Particles
 
@@ -341,6 +343,7 @@
 - Fix for bug where round-tripped glTF imported scenes are encapsulated in a second root node ([#6349](https://github.com/BabylonJS/Babylon.js/issues/6349))([drigax](https://github.com/drigax) & [noalak](https://github.com/noalak))
 - Fix `HDRCubeTexture` construction, `generateHarmonics` was not properly taken into account ([Popov72](https://github.com/Popov72))
 - VideoTexture poster respects invertY ([Sebavan](https://github.com/sebavan/)
+- Fix for bug where round-tripped glTF imported scenes have incorrect light orientation, and duplicated parent nodes ([#7377](https://github.com/BabylonJS/Babylon.js/issues/7377))([drigax](https://github.com/drigax))
 
 ## Breaking changes
 

+ 8 - 5
gui/src/2D/advancedDynamicTexture.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observable, Observer } from "babylonjs/Misc/observable";
-import { Viewport, Color3, Vector2, Vector3, Matrix } from "babylonjs/Maths/math";
+import { Vector2, Vector3, Matrix } from "babylonjs/Maths/math.vector";
 import { Tools } from "babylonjs/Misc/tools";
 import { PointerInfoPre, PointerInfo, PointerEventTypes } from 'babylonjs/Events/pointerEvents';
 import { ClipboardEventTypes, ClipboardInfo } from "babylonjs/Events/clipboardEvents";
@@ -19,6 +19,8 @@ import { Control } from "./controls/control";
 import { Style } from "./style";
 import { Measure } from "./measure";
 import { Constants } from 'babylonjs/Engines/constants';
+import { Viewport } from 'babylonjs/Maths/math.viewport';
+import { Color3 } from 'babylonjs/Maths/math.color';
 /**
 * Interface used to define a control that can receive focus
 */
@@ -635,7 +637,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         this._lastControlDown[pointerId] = control;
         this.onControlPickedObservable.notifyObservers(control);
     }
-    private _doPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): void {
+    private _doPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): void {
         var scene = this.getScene();
         if (!scene) {
             return;
@@ -654,7 +656,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         }
 
         this._cursorChanged = false;
-        if (!this._rootContainer._processPicking(x, y, type, pointerId, buttonIndex)) {
+        if (!this._rootContainer._processPicking(x, y, type, pointerId, buttonIndex, deltaX, deltaY)) {
             this._changeCursor("");
             if (type === PointerEventTypes.POINTERMOVE) {
                 if (this._lastControlOver[pointerId]) {
@@ -701,7 +703,8 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             }
             if (pi.type !== PointerEventTypes.POINTERMOVE
                 && pi.type !== PointerEventTypes.POINTERUP
-                && pi.type !== PointerEventTypes.POINTERDOWN) {
+                && pi.type !== PointerEventTypes.POINTERDOWN
+                && pi.type !== PointerEventTypes.POINTERWHEEL) {
                 return;
             }
             if (!scene) {
@@ -723,7 +726,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             let y = scene.pointerY / engine.getHardwareScalingLevel() - (engine.getRenderHeight() - tempViewport.y - tempViewport.height);
             this._shouldBlockPointer = false;
             // Do picking modifies _shouldBlockPointer
-            this._doPicking(x, y, pi.type, (pi.event as PointerEvent).pointerId || 0, pi.event.button);
+            this._doPicking(x, y, pi.type, (pi.event as PointerEvent).pointerId || 0, pi.event.button, (<MouseWheelEvent>pi.event).deltaX, (<MouseWheelEvent>pi.event).deltaY);
             // Avoid overwriting a true skipOnPointerObservable to false
             if (this._shouldBlockPointer) {
                 pi.skipOnPointerObservable = this._shouldBlockPointer;

+ 3 - 3
gui/src/2D/controls/button.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "babylonjs/types";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 import { Rectangle } from "./rectangle";
 import { Control } from "./control";
@@ -89,7 +89,7 @@ export class Button extends Rectangle {
 
     // While being a container, the button behaves like a control.
     /** @hidden */
-    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean {
+    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): boolean {
         if (!this._isEnabled || !this.isHitTestVisible || !this.isVisible || this.notRenderable) {
             return false;
         }
@@ -113,7 +113,7 @@ export class Button extends Rectangle {
             }
         }
 
-        this._processObservables(type, x, y, pointerId, buttonIndex);
+        this._processObservables(type, x, y, pointerId, buttonIndex, deltaX, deltaY);
 
         return true;
     }

+ 1 - 1
gui/src/2D/controls/checkbox.ts

@@ -1,5 +1,5 @@
 import { Observable } from "babylonjs/Misc/observable";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 import { Control } from "./control";
 import { StackPanel } from "./stackPanel";

+ 2 - 1
gui/src/2D/controls/colorpicker.ts

@@ -1,5 +1,5 @@
 import { Observable } from "babylonjs/Misc/observable";
-import { Color3, Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 import { Control } from "./control";
 import { Measure } from "../measure";
@@ -10,6 +10,7 @@ import { Grid } from "./grid";
 import { AdvancedDynamicTexture } from "../advancedDynamicTexture";
 import { TextBlock } from "../controls/textBlock";
 import { _TypeStore } from 'babylonjs/Misc/typeStore';
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 /** Class used to create color pickers */
 export class ColorPicker extends Control {

+ 3 - 3
gui/src/2D/controls/container.ts

@@ -416,7 +416,7 @@ export class Container extends Control {
     }
 
     /** @hidden */
-    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean {
+    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): boolean {
         if (!this._isEnabled || !this.isVisible || this.notRenderable) {
             return false;
         }
@@ -428,7 +428,7 @@ export class Container extends Control {
         // Checking backwards to pick closest first
         for (var index = this._children.length - 1; index >= 0; index--) {
             var child = this._children[index];
-            if (child._processPicking(x, y, type, pointerId, buttonIndex)) {
+            if (child._processPicking(x, y, type, pointerId, buttonIndex, deltaX, deltaY)) {
                 if (child.hoverCursor) {
                     this._host._changeCursor(child.hoverCursor);
                 }
@@ -440,7 +440,7 @@ export class Container extends Control {
             return false;
         }
 
-        return this._processObservables(type, x, y, pointerId, buttonIndex);
+        return this._processObservables(type, x, y, pointerId, buttonIndex, deltaX, deltaY);
     }
 
     /** @hidden */

+ 26 - 4
gui/src/2D/controls/control.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observable, Observer } from "babylonjs/Misc/observable";
-import { Vector2, Vector3, Matrix } from "babylonjs/Maths/math";
+import { Vector2, Vector3, Matrix } from "babylonjs/Maths/math.vector";
 import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
 import { Logger } from "babylonjs/Misc/logger";
 import { Tools } from "babylonjs/Misc/tools";
@@ -231,6 +231,10 @@ export class Control {
     }
 
     /**
+    * An event triggered when pointer wheel is scrolled
+    */
+    public onWheelObservable = new Observable<Vector2>();
+    /**
     * An event triggered when the pointer move over the control.
     */
     public onPointerMoveObservable = new Observable<Vector2>();
@@ -1683,7 +1687,7 @@ export class Control {
     }
 
     /** @hidden */
-    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean {
+    public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): boolean {
         if (!this._isEnabled) {
             return false;
         }
@@ -1695,7 +1699,7 @@ export class Control {
             return false;
         }
 
-        this._processObservables(type, x, y, pointerId, buttonIndex);
+        this._processObservables(type, x, y, pointerId, buttonIndex, deltaX, deltaY);
 
         return true;
     }
@@ -1797,7 +1801,17 @@ export class Control {
     }
 
     /** @hidden */
-    public _processObservables(type: number, x: number, y: number, pointerId: number, buttonIndex: number): boolean {
+    public _onWheelScroll(deltaX?: number, deltaY?: number): void {
+        if (!this._isEnabled) {
+            return;
+        }
+        var canNotify: boolean = this.onWheelObservable.notifyObservers(new Vector2(deltaX, deltaY));
+
+        if (canNotify && this.parent != null) { this.parent._onWheelScroll(deltaX, deltaY); }
+    }
+
+    /** @hidden */
+    public _processObservables(type: number, x: number, y: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): boolean {
         if (!this._isEnabled) {
             return false;
         }
@@ -1833,6 +1847,13 @@ export class Control {
             return true;
         }
 
+        if (type === PointerEventTypes.POINTERWHEEL) {
+            if (this._host._lastControlOver[pointerId]) {
+                this._host._lastControlOver[pointerId]._onWheelScroll(deltaX, deltaY);
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -1861,6 +1882,7 @@ export class Control {
         this.onPointerOutObservable.clear();
         this.onPointerUpObservable.clear();
         this.onPointerClickObservable.clear();
+        this.onWheelObservable.clear();
 
         if (this._styleObserver && this._style) {
             this._style.onChangedObservable.remove(this._styleObserver);

+ 1 - 1
gui/src/2D/controls/inputText.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observable, Observer } from "babylonjs/Misc/observable";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { ClipboardEventTypes, ClipboardInfo } from "babylonjs/Events/clipboardEvents";
 import { PointerInfo, PointerEventTypes } from 'babylonjs/Events/pointerEvents';
 

+ 1 - 1
gui/src/2D/controls/line.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observer } from "babylonjs/Misc/observable";
-import { Vector3, Matrix } from "babylonjs/Maths/math";
+import { Vector3, Matrix } from "babylonjs/Maths/math.vector";
 import { Tools } from "babylonjs/Misc/tools";
 import { Scene } from "babylonjs/scene";
 

+ 1 - 1
gui/src/2D/controls/radioButton.ts

@@ -1,5 +1,5 @@
 import { Observable } from "babylonjs/Misc/observable";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 import { Control } from "./control";
 import { StackPanel } from "./stackPanel";

+ 11 - 17
gui/src/2D/controls/scrollViewers/scrollViewer.ts

@@ -1,7 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observer } from "babylonjs/Misc/observable";
-import { PointerInfo, PointerEventTypes } from "babylonjs/Events/pointerEvents";
-
+import { Vector2 } from "babylonjs/Maths/math";
 import { Rectangle } from "../rectangle";
 import { Grid } from "../grid";
 import { Image } from "../image";
@@ -36,7 +35,7 @@ export class ScrollViewer extends Rectangle {
     private _window: _ScrollViewerWindow;
     private _pointerIsOver: Boolean = false;
     private _wheelPrecision: number = 0.05;
-    private _onPointerObserver: Nullable<Observer<PointerInfo>>;
+    private _onWheelObserver: Nullable<Observer<Vector2>>;
     private _clientWidth: number;
     private _clientHeight: number;
     private _useImageBar: Boolean;
@@ -45,7 +44,6 @@ export class ScrollViewer extends Rectangle {
     private _barImageHeight: number = 1;
     private _horizontalBarImageHeight: number = 1;
     private _verticalBarImageHeight: number = 1;
-
     /**
      * Gets the horizontal scrollbar
      */
@@ -650,26 +648,25 @@ export class ScrollViewer extends Rectangle {
 
     /** @hidden */
     private _attachWheel() {
-        if (!this._host || this._onPointerObserver) {
+        if (!this._host || this._onWheelObserver) {
             return;
         }
 
-        let scene = this._host.getScene();
-        this._onPointerObserver = scene!.onPointerObservable.add((pi, state) => {
-            if (!this._pointerIsOver || pi.type !== PointerEventTypes.POINTERWHEEL) {
+        this._onWheelObserver = this.onWheelObservable.add((pi) => {
+            if (!this._pointerIsOver) {
                 return;
             }
             if (this._verticalBar.isVisible == true) {
-                if ((<MouseWheelEvent>pi.event).deltaY < 0 && this._verticalBar.value > 0) {
+                if (pi.y < 0 && this._verticalBar.value > 0) {
                     this._verticalBar.value -= this._wheelPrecision;
-                } else if ((<MouseWheelEvent>pi.event).deltaY > 0 && this._verticalBar.value < this._verticalBar.maximum) {
+                } else if (pi.y > 0 && this._verticalBar.value < this._verticalBar.maximum) {
                     this._verticalBar.value += this._wheelPrecision;
                 }
             }
             if (this._horizontalBar.isVisible == true) {
-                if ((<MouseWheelEvent>pi.event).deltaX < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
+                if (pi.x < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
                     this._horizontalBar.value += this._wheelPrecision;
-                } else if ((<MouseWheelEvent>pi.event).deltaX > 0 && this._horizontalBar.value > 0) {
+                } else if (pi.x > 0 && this._horizontalBar.value > 0) {
                     this._horizontalBar.value -= this._wheelPrecision;
                 }
             }
@@ -690,11 +687,8 @@ export class ScrollViewer extends Rectangle {
 
     /** Releases associated resources */
     public dispose() {
-        let scene = this._host.getScene();
-        if (scene && this._onPointerObserver) {
-            scene.onPointerObservable.remove(this._onPointerObserver);
-            this._onPointerObserver = null;
-        }
+        this.onWheelObservable.remove(this._onWheelObserver);
+        this._onWheelObserver = null;
         super.dispose();
     }
 }

+ 1 - 1
gui/src/2D/controls/sliders/baseSlider.ts

@@ -1,5 +1,5 @@
 import { Observable } from "babylonjs/Misc/observable";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 import { Control } from "../control";
 import { ValueAndUnit } from "../../valueAndUnit";

+ 1 - 1
gui/src/2D/controls/sliders/imageScrollBar.ts

@@ -1,4 +1,4 @@
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { BaseSlider } from "./baseSlider";
 import { Control } from "../control";
 import { Image } from "../image";

+ 1 - 1
gui/src/2D/controls/sliders/scrollBar.ts

@@ -1,4 +1,4 @@
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { BaseSlider } from "./baseSlider";
 import { Control } from "../control";
 import { Measure } from "../../measure";

+ 2 - 1
gui/src/2D/math2D.ts

@@ -1,5 +1,6 @@
 import { Nullable } from "babylonjs/types";
-import { Vector2, Epsilon } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
+import { Epsilon } from 'babylonjs/Maths/math.constants';
 
 /**
  * Class used to transport Vector2 information for pointer events

+ 1 - 1
gui/src/2D/measure.ts

@@ -1,5 +1,5 @@
 import { Matrix2D } from "./math2D";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 let tmpRect = [
     new Vector2(0, 0),

+ 1 - 1
gui/src/2D/multiLinePoint.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observer } from "babylonjs/Misc/observable";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { Camera } from "babylonjs/Cameras/camera";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 

+ 2 - 1
gui/src/3D/controls/button3D.ts

@@ -1,5 +1,5 @@
 import { int, Nullable } from "babylonjs/types";
-import { Color3, Vector4 } from "babylonjs/Maths/math";
+import { Vector4 } from "babylonjs/Maths/math.vector";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { BoxBuilder } from "babylonjs/Meshes/Builders/boxBuilder";
@@ -11,6 +11,7 @@ import { Scene } from "babylonjs/scene";
 import { AbstractButton3D } from "./abstractButton3D";
 import { AdvancedDynamicTexture } from "../../2D/advancedDynamicTexture";
 import { Control } from "../../2D/controls/control";
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 /**
  * Class used to create a button in 3D

+ 1 - 1
gui/src/3D/controls/control3D.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observable } from "babylonjs/Misc/observable";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { PointerEventTypes } from "babylonjs/Events/pointerEvents";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";

+ 2 - 1
gui/src/3D/controls/holographicButton.ts

@@ -2,7 +2,7 @@ import { Button3D } from "./button3D";
 
 import { Nullable } from "babylonjs/types";
 import { Observer } from "babylonjs/Misc/observable";
-import { Color3, Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
 import { Mesh } from "babylonjs/Meshes/mesh";
@@ -17,6 +17,7 @@ import { Image } from "../../2D/controls/image";
 import { TextBlock } from "../../2D/controls/textBlock";
 import { AdvancedDynamicTexture } from "../../2D/advancedDynamicTexture";
 import { Control3D } from "./control3D";
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 /**
  * Class used to create a holographic button in 3D

+ 1 - 1
gui/src/3D/controls/planePanel.ts

@@ -1,4 +1,4 @@
-import { TmpVectors, Vector3 } from "babylonjs/Maths/math";
+import { TmpVectors, Vector3 } from "babylonjs/Maths/math.vector";
 
 import { Container3D } from "./container3D";
 import { Control3D } from "./control3D";

+ 1 - 1
gui/src/3D/controls/scatterPanel.ts

@@ -1,5 +1,5 @@
 import { Tools } from "babylonjs/Misc/tools";
-import { TmpVectors, Vector3 } from "babylonjs/Maths/math";
+import { TmpVectors, Vector3 } from "babylonjs/Maths/math.vector";
 import { float } from "babylonjs/types";
 
 import { VolumeBasedPanel } from "./volumeBasedPanel";

+ 2 - 1
gui/src/3D/controls/spherePanel.ts

@@ -1,10 +1,11 @@
 import { Tools } from "babylonjs/Misc/tools";
-import { Space, Axis, Matrix, TmpVectors, Vector3 } from "babylonjs/Maths/math";
+import { Matrix, TmpVectors, Vector3 } from "babylonjs/Maths/math.vector";
 import { float } from "babylonjs/types";
 
 import { VolumeBasedPanel } from "./volumeBasedPanel";
 import { Control3D } from "./control3D";
 import { Container3D } from "./container3D";
+import { Axis, Space } from 'babylonjs/Maths/math.axis';
 
 /**
  * Class used to create a container panel deployed on the surface of a sphere

+ 1 - 1
gui/src/3D/controls/stackPanel3D.ts

@@ -1,5 +1,5 @@
 import { Tools } from "babylonjs/Misc/tools";
-import { Matrix, TmpVectors, Vector3 } from "babylonjs/Maths/math";
+import { Matrix, TmpVectors, Vector3 } from "babylonjs/Maths/math.vector";
 
 import { Container3D } from "./container3D";
 

+ 1 - 1
gui/src/3D/gui3DManager.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { Observable, Observer } from "babylonjs/Misc/observable";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { PointerInfo, PointerEventTypes } from 'babylonjs/Events/pointerEvents';
 import { Material } from "babylonjs/Materials/material";
 import { HemisphericLight } from "babylonjs/Lights/hemisphericLight";

+ 2 - 1
gui/src/3D/materials/fluentMaterial.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { serializeAsColor4, serializeAsVector3, serializeAsTexture, serialize, expandToProperty, serializeAsColor3, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3, Vector3, Color4, Matrix, TmpVectors } from "babylonjs/Maths/math";
+import { Vector3, Matrix, TmpVectors } from "babylonjs/Maths/math.vector";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { MaterialDefines } from "babylonjs/Materials/materialDefines";
 import { IEffectCreationOptions } from "babylonjs/Materials/effect";
@@ -12,6 +12,7 @@ import { SubMesh } from "babylonjs/Meshes/subMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { Scene } from "babylonjs/scene";
 import { _TypeStore } from 'babylonjs/Misc/typeStore';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 
 import "./shaders/fluent.vertex";
 import "./shaders/fluent.fragment";

+ 1 - 1
gui/src/3D/vector3WithInfo.ts

@@ -1,4 +1,4 @@
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 
 /**
  * Class used to transport Vector3 information for pointer events

+ 5 - 5
inspector/src/components/actionTabs/lines/color3LineComponent.tsx

@@ -1,10 +1,10 @@
 import * as React from "react";
 import { Observable } from "babylonjs/Misc/observable";
-import { Color3 } from "babylonjs/Maths/math";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
 import { NumericInputComponent } from "./numericInputComponent";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 const copyIcon: string = require("./copy.svg");
 
@@ -124,7 +124,7 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
 
     render() {
 
-        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />;
         const colorAsColor3 = this.state.color.getClassName() === "Color3" ? this.state.color : new Color3(this.state.color.r, this.state.color.g, this.state.color.b);
 
         return (
@@ -146,9 +146,9 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
                 {
                     this.state.isExpanded &&
                     <div className="secondLine">
-                        <NumericInputComponent label="r" value={this.state.color.r} onChange={value => this.updateStateR(value)} />
-                        <NumericInputComponent label="g" value={this.state.color.g} onChange={value => this.updateStateG(value)} />
-                        <NumericInputComponent label="b" value={this.state.color.b} onChange={value => this.updateStateB(value)} />
+                        <NumericInputComponent label="r" value={this.state.color.r} onChange={(value) => this.updateStateR(value)} />
+                        <NumericInputComponent label="g" value={this.state.color.g} onChange={(value) => this.updateStateG(value)} />
+                        <NumericInputComponent label="b" value={this.state.color.b} onChange={(value) => this.updateStateB(value)} />
                     </div>
                 }
             </div>

+ 1 - 1
inspector/src/components/actionTabs/lines/color4LineComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import { Observable } from "babylonjs/Misc/observable";
-import { Color3, Color4 } from "babylonjs/Maths/math";
+import { Color3, Color4 } from "babylonjs/Maths/math.color";
 import { NumericInputComponent } from "./numericInputComponent";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";

+ 1 - 1
inspector/src/components/actionTabs/lines/quaternionLineComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import { Observable } from "babylonjs/Misc/observable";
-import { Quaternion, Vector3 } from "babylonjs/Maths/math";
+import { Quaternion, Vector3 } from "babylonjs/Maths/math.vector";
 import { NumericInputComponent } from "./numericInputComponent";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";

+ 1 - 1
inspector/src/components/actionTabs/lines/vector2LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 1 - 1
inspector/src/components/actionTabs/lines/vector3LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 1 - 1
inspector/src/components/actionTabs/lines/vector4LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector4 } from "babylonjs/Maths/math";
+import { Vector4 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 10 - 9
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/meshPropertyGridComponent.tsx

@@ -2,7 +2,8 @@ import * as React from "react";
 
 import { Observable } from "babylonjs/Misc/observable";
 import { Tools } from "babylonjs/Misc/tools";
-import { Color3, Vector3, TmpVectors } from "babylonjs/Maths/math";
+import { Vector3, TmpVectors } from "babylonjs/Maths/math.vector";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { VertexBuffer } from "babylonjs/Meshes/buffer";
 import { LinesBuilder } from "babylonjs/Meshes/Builders/linesBuilder";
@@ -292,12 +293,12 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
             return {
                 label: m.name || "no name",
                 value: i
-            }});
+            };});
 
         materialOptions.splice(0, 0, {
             label: "None",
             value: -1
-        })
+        });
 
         return (
             <div className="pane">
@@ -314,7 +315,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                     {
                         mesh.parent &&
                         <TextLineComponent label="Parent" value={mesh.parent.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(mesh.parent)}/>
-                    }                      
+                    }
                     {
                         mesh.skeleton &&
                         <TextLineComponent label="Skeleton" value={mesh.skeleton.name} onLink={() => this.onSkeletonLink()}/>
@@ -326,8 +327,8 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                         <TextLineComponent label="Link to material" value={mesh.material.name} onLink={() => this.onMaterialLink()} />
                     }
                     {   !mesh.isAnInstance &&
-                        <OptionsLineComponent label="Active material" options={materialOptions} 
-                            target={mesh} propertyName="material" 
+                        <OptionsLineComponent label="Active material" options={materialOptions}
+                            target={mesh} propertyName="material"
                             noDirectUpdate={true}
                             onSelect={(value: number) => {
                                 if (value < 0) {
@@ -376,7 +377,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                     {
                         mesh.isVerticesDataPresent(VertexBuffer.ColorKind) &&
                         <CheckBoxLineComponent label="Has vertex alpha" target={mesh} propertyName="hasVertexAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    }                    
+                    }
                     {
                         scene.fogMode !== Scene.FOGMODE_NONE &&
                         <CheckBoxLineComponent label="Apply fog" target={mesh} propertyName="applyFog" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
@@ -393,7 +394,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                             morphTargets.map((mt, i) => {
                                 return (
                                     <SliderLineComponent label={mt.name} target={mt} propertyName="influence" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                                )
+                                );
                             })
                         }
                     </LineContainerComponent>
@@ -430,7 +431,7 @@ export class MeshPropertyGridComponent extends React.Component<IMeshPropertyGrid
                     <OptionsLineComponent label="Algorithm" options={algorithmOptions} target={mesh} propertyName="occlusionQueryAlgorithmType" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="EDGE RENDERING" closed={true}>
-                    <CheckBoxLineComponent label="Enable" target={mesh} isSelected={() => mesh.edgesRenderer != null} onSelect={value => {
+                    <CheckBoxLineComponent label="Enable" target={mesh} isSelected={() => mesh.edgesRenderer != null} onSelect={(value) => {
                         if (value) {
                             mesh.enableEdgesRendering();
                         } else {

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/renderGridPropertyGridComponent.tsx

@@ -1,7 +1,7 @@
 import * as React from "react";
 
 import { Nullable } from "babylonjs/types";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { Texture } from "babylonjs/Materials/Textures/texture";

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx

@@ -3,7 +3,7 @@ import * as React from "react";
 import { Nullable } from "babylonjs/types";
 import { Observable } from "babylonjs/Misc/observable";
 import { Tools } from "babylonjs/Misc/tools";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { CubeTexture } from "babylonjs/Materials/Textures/cubeTexture";
 import { ImageProcessingConfiguration } from "babylonjs/Materials/imageProcessingConfiguration";

+ 1 - 1
loaders/src/OBJ/mtlFileLoader.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "babylonjs/types";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 

+ 2 - 1
loaders/src/OBJ/objFileLoader.ts

@@ -1,5 +1,6 @@
 import { FloatArray, IndicesArray } from "babylonjs/types";
-import { Vector3, Vector2, Color4 } from "babylonjs/Maths/math";
+import { Vector3, Vector2 } from "babylonjs/Maths/math.vector";
+import { Color4 } from 'babylonjs/Maths/math.color';
 import { Tools } from "babylonjs/Misc/tools";
 import { VertexData } from "babylonjs/Meshes/mesh.vertexData";
 import { Geometry } from "babylonjs/Meshes/geometry";

File diff suppressed because it is too large
+ 2 - 1
loaders/src/glTF/1.0/glTFLoader.ts


+ 2 - 1
loaders/src/glTF/1.0/glTFLoaderUtils.ts

@@ -1,7 +1,8 @@
 import { IGLTFTechniqueParameter, EParameterType, ETextureWrapMode, IGLTFAccessor, ETextureFilterType, IGLTFRuntime, IGLTFBufferView, EComponentType } from "./glTFLoaderInterfaces";
 
 import { Nullable } from "babylonjs/types";
-import { Vector2, Vector3, Vector4, Color4, Matrix } from "babylonjs/Maths/math";
+import { Vector2, Vector3, Vector4, Matrix } from "babylonjs/Maths/math.vector";
+import { Color4 } from 'babylonjs/Maths/math.color';
 import { Effect } from "babylonjs/Materials/effect";
 import { ShaderMaterial } from "babylonjs/Materials/shaderMaterial";
 import { Texture } from "babylonjs/Materials/Textures/texture";

+ 2 - 1
loaders/src/glTF/1.0/glTFMaterialsCommonExtension.ts

@@ -3,7 +3,8 @@ import { GLTFLoaderBase } from "./glTFLoader";
 
 import { IGLTFRuntime, IGLTFMaterial } from "./glTFLoaderInterfaces";
 
-import { Color3, Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { Tools } from "babylonjs/Misc/tools";
 import { Material } from "babylonjs/Materials/material";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";

+ 1 - 1
loaders/src/glTF/2.0/Extensions/EXT_lights_image_based.ts

@@ -1,7 +1,7 @@
 import { Nullable } from "babylonjs/types";
 import { Scalar } from "babylonjs/Maths/math.scalar";
 import { SphericalHarmonics, SphericalPolynomial } from "babylonjs/Maths/sphericalPolynomial";
-import { Quaternion, Matrix } from "babylonjs/Maths/math";
+import { Quaternion, Matrix } from "babylonjs/Maths/math.vector";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { RawCubeTexture } from "babylonjs/Materials/Textures/rawCubeTexture";
 

+ 2 - 1
loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts

@@ -1,5 +1,6 @@
 import { Nullable } from "babylonjs/types";
-import { Color3, Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { DirectionalLight } from "babylonjs/Lights/directionalLight";
 import { PointLight } from "babylonjs/Lights/pointLight";
 import { SpotLight } from "babylonjs/Lights/spotLight";

+ 1 - 1
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "babylonjs/types";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { PBRMaterial } from "babylonjs/Materials/PBR/pbrMaterial";
 import { Material } from "babylonjs/Materials/material";
 

+ 1 - 1
loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "babylonjs/types";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { PBRMaterial } from "babylonjs/Materials/PBR/pbrMaterial";
 import { Material } from "babylonjs/Materials/material";
 

+ 1 - 1
loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts

@@ -1,5 +1,5 @@
 import { Nullable } from "babylonjs/types";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { Tools } from "babylonjs/Misc/tools";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 import { AnimationEvent } from "babylonjs/Animations/animationEvent";

+ 2 - 1
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1,6 +1,7 @@
 import { IndicesArray, Nullable } from "babylonjs/types";
 import { Deferred } from "babylonjs/Misc/deferred";
-import { Quaternion, Color3, Vector3, Matrix } from "babylonjs/Maths/math";
+import { Quaternion, Vector3, Matrix } from "babylonjs/Maths/math.vector";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { Tools } from "babylonjs/Misc/tools";
 import { IFileRequest } from "babylonjs/Misc/fileRequest";
 import { Camera } from "babylonjs/Cameras/camera";

+ 5 - 5
nodeEditor/src/sharedComponents/color3LineComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import { Observable } from "babylonjs/Misc/observable";
-import { Color3, Color4 } from "babylonjs/Maths/math";
+import { Color3, Color4 } from "babylonjs/Maths/math.color";
 import { PropertyChangedEvent } from "./propertyChangedEvent";
 import { NumericInputComponent } from "./numericInputComponent";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -138,7 +138,7 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
 
     render() {
 
-        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />;
         const colorAsColor3 = this.state.color.getClassName() === "Color3" ? this.state.color : new Color3(this.state.color.r, this.state.color.g, this.state.color.b);
 
         return (
@@ -160,9 +160,9 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
                 {
                     this.state.isExpanded &&
                     <div className="secondLine">
-                        <NumericInputComponent globalState={this.props.globalState} label="r" value={this.state.color.r} onChange={value => this.updateStateR(value)} />
-                        <NumericInputComponent globalState={this.props.globalState} label="g" value={this.state.color.g} onChange={value => this.updateStateG(value)} />
-                        <NumericInputComponent globalState={this.props.globalState} label="b" value={this.state.color.b} onChange={value => this.updateStateB(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="r" value={this.state.color.r} onChange={(value) => this.updateStateR(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="g" value={this.state.color.g} onChange={(value) => this.updateStateG(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="b" value={this.state.color.b} onChange={(value) => this.updateStateB(value)} />
                     </div>
                 }
             </div>

+ 6 - 6
nodeEditor/src/sharedComponents/color4LineComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import { Observable } from "babylonjs/Misc/observable";
-import { Color3, Color4 } from "babylonjs/Maths/math";
+import { Color3, Color4 } from "babylonjs/Maths/math.color";
 import { PropertyChangedEvent } from "./propertyChangedEvent";
 import { NumericInputComponent } from "./numericInputComponent";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -149,7 +149,7 @@ export class Color4LineComponent extends React.Component<IColor4LineComponentPro
 
     render() {
 
-        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />;
         const colorAsColor3 = new Color3(this.state.color.r, this.state.color.g, this.state.color.b);
 
         return (
@@ -171,10 +171,10 @@ export class Color4LineComponent extends React.Component<IColor4LineComponentPro
                 {
                     this.state.isExpanded &&
                     <div className="secondLine">
-                        <NumericInputComponent globalState={this.props.globalState} label="r" value={this.state.color.r} onChange={value => this.updateStateR(value)} />
-                        <NumericInputComponent globalState={this.props.globalState} label="g" value={this.state.color.g} onChange={value => this.updateStateG(value)} />
-                        <NumericInputComponent globalState={this.props.globalState} label="b" value={this.state.color.b} onChange={value => this.updateStateB(value)} />
-                        <NumericInputComponent globalState={this.props.globalState} label="a" value={this.state.color.a} onChange={value => this.updateStateA(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="r" value={this.state.color.r} onChange={(value) => this.updateStateR(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="g" value={this.state.color.g} onChange={(value) => this.updateStateG(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="b" value={this.state.color.b} onChange={(value) => this.updateStateB(value)} />
+                        <NumericInputComponent globalState={this.props.globalState} label="a" value={this.state.color.a} onChange={(value) => this.updateStateA(value)} />
                     </div>
                 }
             </div>

+ 1 - 1
nodeEditor/src/sharedComponents/matrixLineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector3, Matrix, Vector4, Quaternion } from "babylonjs/Maths/math";
+import { Vector3, Matrix, Vector4, Quaternion } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 import { PropertyChangedEvent } from "./propertyChangedEvent";
 import { Vector4LineComponent } from './vector4LineComponent';

+ 1 - 1
nodeEditor/src/sharedComponents/vector2LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector2 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 1 - 1
nodeEditor/src/sharedComponents/vector3LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 1 - 1
nodeEditor/src/sharedComponents/vector4LineComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { Vector4 } from "babylonjs/Maths/math";
+import { Vector4 } from "babylonjs/Maths/math.vector";
 import { Observable } from "babylonjs/Misc/observable";
 
 import { NumericInputComponent } from "./numericInputComponent";

+ 1 - 1
package.json

@@ -80,7 +80,7 @@
         "mini-css-extract-plugin": "^0.4.1",
         "minimist": "^1.2.0",
         "mocha": "^5.2.0",
-        "node-sass": "^4.10.0",
+        "node-sass": "^4.13.1",
         "plugin-error": "^1.0.1",
         "prompt": "^1.0.0",
         "re-resizable": "~4.9.1",

+ 1 - 1
postProcessLibrary/src/digitalRain/digitalRainPostProcess.ts

@@ -1,6 +1,6 @@
 import { Nullable } from "babylonjs/types";
 import { serialize, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Matrix } from "babylonjs/Maths/math";
+import { Matrix } from "babylonjs/Maths/math.vector";
 import { Camera } from "babylonjs/Cameras/camera";
 import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
 import { Texture } from "babylonjs/Materials/Textures/texture";

+ 1 - 1
proceduralTexturesLibrary/src/brick/brickProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serialize, serializeAsColor3, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
proceduralTexturesLibrary/src/cloud/cloudProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serializeAsColor4, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color4 } from "babylonjs/Maths/math";
+import { Color4 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 2 - 1
proceduralTexturesLibrary/src/fire/fireProceduralTexture.ts

@@ -1,5 +1,6 @@
 import { serialize, serializeAsVector2, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Vector2, Color3 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
+import { Color3 } from 'babylonjs/Maths/math.color';
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
proceduralTexturesLibrary/src/grass/grassProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serializeAsColor3, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
proceduralTexturesLibrary/src/marble/marbleProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serialize, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
proceduralTexturesLibrary/src/road/roadProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serializeAsColor3, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
proceduralTexturesLibrary/src/wood/woodProceduralTexture.ts

@@ -1,5 +1,5 @@
 import { serialize, serializeAsColor3, SerializationHelper } from "babylonjs/Misc/decorators";
-import { Color3 } from "babylonjs/Maths/math";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
 import { Scene } from "babylonjs/scene";

+ 1 - 1
serializers/src/OBJ/objSerializer.ts

@@ -1,6 +1,6 @@
 
 import { Nullable } from "babylonjs/types";
-import { Matrix } from "babylonjs/Maths/math";
+import { Matrix } from "babylonjs/Maths/math.vector";
 import { Tools } from "babylonjs/Misc/tools";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { Geometry } from "babylonjs/Meshes/geometry";

+ 230 - 175
serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts

@@ -1,176 +1,231 @@
-import { SpotLight } from "babylonjs/Lights/spotLight";
-import { Vector3, Color3, Quaternion } from "babylonjs/Maths/math";
-import { Light } from "babylonjs/Lights/light";
-import { Node } from "babylonjs/node";
-import { ShadowLight } from "babylonjs/Lights/shadowLight";
-import { IChildRootProperty } from "babylonjs-gltf2interface";
-import { INode } from "babylonjs-gltf2interface";
-import { IGLTFExporterExtensionV2 } from "../glTFExporterExtension";
-import { _Exporter } from "../glTFExporter";
-import { Logger } from "babylonjs/Misc/logger";
-import { _GLTFUtilities } from "../glTFUtilities";
-
-const NAME = "KHR_lights_punctual";
-
-enum LightType {
-    DIRECTIONAL = "directional",
-    POINT = "point",
-    SPOT = "spot"
-}
-
-interface ILightReference {
-    light: number;
-}
-
-interface ILight extends IChildRootProperty {
-    type: LightType;
-    color?: number[];
-    intensity?: number;
-    range?: number;
-    spot?: {
-        innerConeAngle?: number;
-        outerConeAngle?: number;
-    };
-}
-
-interface ILights {
-    lights: ILight[];
-}
-
-/**
- * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md)
- */
-export class KHR_lights_punctual implements IGLTFExporterExtensionV2 {
-    /** The name of this extension. */
-    public readonly name = NAME;
-
-    /** Defines whether this extension is enabled. */
-    public enabled = true;
-
-    /** Defines whether this extension is required */
-    public required = false;
-
-    /** Reference to the glTF exporter */
-    private _exporter: _Exporter;
-
-    private _lights: ILights;
-
-    /** @hidden */
-    constructor(exporter: _Exporter) {
-        this._exporter = exporter;
-    }
-
-    /** @hidden */
-    public dispose() {
-        delete this._lights;
-    }
-
-    /** @hidden */
-    public get wasUsed() {
-        return !!this._lights;
-    }
-
-    /** @hidden */
-    public onExporting(): void {
-        this._exporter!._glTF.extensions![NAME] = this._lights;
-    }
-    /**
-     * Define this method to modify the default behavior when exporting a node
-     * @param context The context when exporting the node
-     * @param node glTF node
-     * @param babylonNode BabylonJS node
-     * @returns nullable INode promise
-     */
-    public postExportNodeAsync(context: string, node: INode, babylonNode: Node): Promise<INode> {
-        return new Promise((resolve, reject) => {
-            if (babylonNode instanceof ShadowLight) {
-                const babylonLight: ShadowLight = babylonNode;
-                let light: ILight;
-
-                const lightType = (
-                    babylonLight.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT ? LightType.POINT : (
-                        babylonLight.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT ? LightType.DIRECTIONAL : (
-                            babylonLight.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT ? LightType.SPOT : null
-                        )));
-                if (lightType == null) {
-                    Logger.Warn(`${context}: Light ${babylonLight.name} is not supported in ${NAME}`);
-                }
-                else {
-                    const lightPosition = babylonLight.position.clone();
-                    let convertToRightHandedSystem = this._exporter._convertToRightHandedSystemMap[babylonNode.uniqueId];
-                    if (!lightPosition.equals(Vector3.Zero())) {
-                        if (convertToRightHandedSystem) {
-                            _GLTFUtilities._GetRightHandedPositionVector3FromRef(lightPosition);
-                        }
-                        node.translation = lightPosition.asArray();
-                    }
-                    if (lightType !== LightType.POINT) {
-                        const localAxis = babylonLight.direction;
-                        const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2;
-                        const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z);
-                        const pitch = -Math.atan2(localAxis.y, len);
-                        const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw, pitch, 0);
-                        if (convertToRightHandedSystem) {
-                            _GLTFUtilities._GetRightHandedQuaternionFromRef(lightRotationQuaternion);
-                        }
-                        if (!lightRotationQuaternion.equals(Quaternion.Identity())) {
-                            node.rotation = lightRotationQuaternion.asArray();
-                        }
-                    }
-
-                    if (babylonLight.falloffType !== Light.FALLOFF_GLTF) {
-                        Logger.Warn(`${context}: Light falloff for ${babylonLight.name} does not match the ${NAME} specification!`);
-                    }
-                    light = {
-                        type: lightType
-                    };
-                    if (!babylonLight.diffuse.equals(Color3.White())) {
-                        light.color = babylonLight.diffuse.asArray();
-                    }
-                    if (babylonLight.intensity !== 1.0) {
-                        light.intensity = babylonLight.intensity;
-                    }
-                    if (babylonLight.range !== Number.MAX_VALUE) {
-                        light.range = babylonLight.range;
-                    }
-
-                    if (lightType === LightType.SPOT) {
-                        const babylonSpotLight = babylonLight as SpotLight;
-                        if (babylonSpotLight.angle !== Math.PI / 2.0) {
-                            if (light.spot == null) {
-                                light.spot = {};
-                            }
-                            light.spot.outerConeAngle = babylonSpotLight.angle / 2.0;
-                        }
-                        if (babylonSpotLight.innerAngle !== 0) {
-                            if (light.spot == null) {
-                                light.spot = {};
-                            }
-                            light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0;
-                        }
-                    }
-
-                    if (this._lights == null) {
-                        this._lights = {
-                            lights: []
-                        };
-                    }
-
-                    this._lights.lights.push(light);
-
-                    if (node.extensions == null) {
-                        node.extensions = {};
-                    }
-                    const lightReference: ILightReference = {
-                        light: this._lights.lights.length - 1
-                    };
-
-                    node.extensions[NAME] = lightReference;
-                }
-            }
-            resolve(node);
-        });
-    }
-}
-
+import { SpotLight } from "babylonjs/Lights/spotLight";
+import { Nullable } from "babylonjs/types";
+import { Vector3, Quaternion } from "babylonjs/Maths/math.vector";
+import { Color3 } from "babylonjs/Maths/math.color";
+import { Light } from "babylonjs/Lights/light";
+import { DirectionalLight } from "babylonjs/Lights/directionalLight"
+import { Node } from "babylonjs/node";
+import { ShadowLight } from "babylonjs/Lights/shadowLight";
+import { IChildRootProperty } from "babylonjs-gltf2interface";
+import { INode } from "babylonjs-gltf2interface";
+import { IGLTFExporterExtensionV2 } from "../glTFExporterExtension";
+import { _Exporter } from "../glTFExporter";
+import { Logger } from "babylonjs/Misc/logger";
+import { _GLTFUtilities } from "../glTFUtilities";
+
+const NAME = "KHR_lights_punctual";
+
+enum LightType {
+    DIRECTIONAL = "directional",
+    POINT = "point",
+    SPOT = "spot"
+}
+
+interface ILightReference {
+    light: number;
+}
+
+interface ILight extends IChildRootProperty {
+    type: LightType;
+    color?: number[];
+    intensity?: number;
+    range?: number;
+    spot?: {
+        innerConeAngle?: number;
+        outerConeAngle?: number;
+    };
+}
+
+interface ILights {
+    lights: ILight[];
+}
+
+/**
+ * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md)
+ */
+export class KHR_lights_punctual implements IGLTFExporterExtensionV2 {
+    /** The name of this extension. */
+    public readonly name = NAME;
+
+    /** Defines whether this extension is enabled. */
+    public enabled = true;
+
+    /** Defines whether this extension is required */
+    public required = false;
+
+    /** Reference to the glTF exporter */
+    private _exporter: _Exporter;
+
+    private _lights: ILights;
+
+    /** @hidden */
+    constructor(exporter: _Exporter) {
+        this._exporter = exporter;
+    }
+
+    /** @hidden */
+    public dispose() {
+        delete this._lights;
+    }
+
+    /** @hidden */
+    public get wasUsed() {
+        return !!this._lights;
+    }
+
+    /** @hidden */
+    public onExporting(): void {
+        this._exporter!._glTF.extensions![NAME] = this._lights;
+    }
+    /**
+     * Define this method to modify the default behavior when exporting a node
+     * @param context The context when exporting the node
+     * @param node glTF node
+     * @param babylonNode BabylonJS node
+     * @param nodeMap Node mapping of unique id to glTF node index
+     * @returns nullable INode promise
+     */
+    public postExportNodeAsync(context: string, node: Nullable<INode>, babylonNode: Node, nodeMap?: {[key: number]: number}): Promise<Nullable<INode>> {
+        return new Promise((resolve, reject) => {
+            if (node && babylonNode instanceof ShadowLight) {
+                const babylonLight: ShadowLight = babylonNode;
+                let light: ILight;
+
+                const lightType = (
+                    babylonLight.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT ? LightType.POINT : (
+                        babylonLight.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT ? LightType.DIRECTIONAL : (
+                            babylonLight.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT ? LightType.SPOT : null
+                        )));
+                if (lightType == null) {
+                    Logger.Warn(`${context}: Light ${babylonLight.name} is not supported in ${NAME}`);
+                }
+                else {
+                    const lightPosition = babylonLight.position.clone();
+                    let convertToRightHandedSystem = this._exporter._convertToRightHandedSystemMap[babylonNode.uniqueId];
+                    if (!lightPosition.equals(Vector3.Zero())) {
+                        if (convertToRightHandedSystem) {
+                            _GLTFUtilities._GetRightHandedPositionVector3FromRef(lightPosition);
+                        }
+                        node.translation = lightPosition.asArray();
+                    }
+                    if (lightType !== LightType.POINT) {
+                        const localAxis = babylonLight.direction;
+                        const yaw = -Math.atan2(localAxis.z * (this._exporter._babylonScene.useRightHandedSystem ? -1 : 1), localAxis.x) + Math.PI / 2;
+                        const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z);
+                        const pitch = -Math.atan2(localAxis.y, len);
+                        const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw, pitch, 0);
+                        if (convertToRightHandedSystem) {
+                            _GLTFUtilities._GetRightHandedQuaternionFromRef(lightRotationQuaternion);
+                        }
+                        if (!lightRotationQuaternion.equals(Quaternion.Identity())) {
+                            node.rotation = lightRotationQuaternion.asArray();
+                        }
+                    }
+
+                    if (babylonLight.falloffType !== Light.FALLOFF_GLTF) {
+                        Logger.Warn(`${context}: Light falloff for ${babylonLight.name} does not match the ${NAME} specification!`);
+                    }
+                    light = {
+                        type: lightType
+                    };
+                    if (!babylonLight.diffuse.equals(Color3.White())) {
+                        light.color = babylonLight.diffuse.asArray();
+                    }
+                    if (babylonLight.intensity !== 1.0) {
+                        light.intensity = babylonLight.intensity;
+                    }
+                    if (babylonLight.range !== Number.MAX_VALUE) {
+                        light.range = babylonLight.range;
+                    }
+
+                    if (lightType === LightType.SPOT) {
+                        const babylonSpotLight = babylonLight as SpotLight;
+                        if (babylonSpotLight.angle !== Math.PI / 2.0) {
+                            if (light.spot == null) {
+                                light.spot = {};
+                            }
+                            light.spot.outerConeAngle = babylonSpotLight.angle / 2.0;
+                        }
+                        if (babylonSpotLight.innerAngle !== 0) {
+                            if (light.spot == null) {
+                                light.spot = {};
+                            }
+                            light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0;
+                        }
+                    }
+
+                    if (this._lights == null) {
+                        this._lights = {
+                            lights: []
+                        };
+                    }
+
+                    this._lights.lights.push(light);
+
+                    const lightReference: ILightReference = {
+                        light: this._lights.lights.length - 1
+                    };
+
+                    // Avoid duplicating the Light's parent node if possible.
+                    let parentBabylonNode = babylonNode.parent;
+                    if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) {
+                        let parentNode = this._exporter._nodes[nodeMap![parentBabylonNode.uniqueId]];
+                        if (parentNode) {
+                            let parentNodeLocalMatrix = TmpVectors.Matrix[0];
+                            let parentInvertNodeLocalMatrix = TmpVectors.Matrix[1];
+                            let parentNodeLocalTranslation = parentNode.translation ? new Vector3(parentNode.translation[0], parentNode.translation[1], parentNode.translation[2]) : Vector3.Zero();
+                            let parentNodeLocalRotation = parentNode.rotation ? new Quaternion(parentNode.rotation[0], parentNode.rotation[1], parentNode.rotation[2], parentNode.rotation[3]) : Quaternion.Identity();
+                            let parentNodeLocalScale = parentNode.scale ? new Vector3(parentNode.scale[0], parentNode.scale[1], parentNode.scale[2]) : Vector3.One();
+
+                            Matrix.ComposeToRef(parentNodeLocalScale, parentNodeLocalRotation, parentNodeLocalTranslation, parentNodeLocalMatrix);
+                            parentNodeLocalMatrix.invertToRef(parentInvertNodeLocalMatrix);
+
+                            // Convert light local matrix to local matrix relative to grandparent, facing -Z
+                            let lightLocalMatrix = TmpVectors.Matrix[2];
+                            let nodeLocalTranslation = node.translation ? new Vector3(node.translation[0], node.translation[1], node.translation[2]) : Vector3.Zero();
+
+                            // Undo directional light positional offset
+                            if (babylonLight instanceof DirectionalLight) {
+                                nodeLocalTranslation.subtractInPlace(this._exporter._babylonScene.useRightHandedSystem ? babylonLight.direction : _GLTFUtilities._GetRightHandedPositionVector3(babylonLight.direction));
+                            }
+                            let nodeLocalRotation = this._exporter._babylonScene.useRightHandedSystem ? Quaternion.Identity() : new Quaternion(0, 1, 0, 0);
+                            if (node.rotation) {
+                                nodeLocalRotation.multiplyInPlace(new Quaternion(node.rotation[0], node.rotation[1], node.rotation[2], node.rotation[3]));
+                            }
+                            let nodeLocalScale = node.scale ? new Vector3(node.scale[0], node.scale[1], node.scale[2]) : Vector3.One();
+
+                            Matrix.ComposeToRef(nodeLocalScale, nodeLocalRotation, nodeLocalTranslation, lightLocalMatrix);
+                            lightLocalMatrix.multiplyToRef(parentInvertNodeLocalMatrix, lightLocalMatrix);
+                            let parentNewScale = TmpVectors.Vector3[0];
+                            let parentNewRotationQuaternion = TmpVectors.Quaternion[0];
+                            let parentNewTranslation = TmpVectors.Vector3[1];
+
+                            lightLocalMatrix.decompose(parentNewScale, parentNewRotationQuaternion, parentNewTranslation);
+                            parentNode.scale = parentNewScale.asArray();
+                            parentNode.rotation = parentNewRotationQuaternion.asArray();
+                            parentNode.translation = parentNewTranslation.asArray();
+
+                            if (parentNode.extensions == null) {
+                                parentNode.extensions = {};
+                            }
+                            parentNode.extensions[NAME] = lightReference;
+
+                            // Do not export the original node
+                            resolve(undefined);
+                            return;
+                        }
+                    }
+
+                    if (node.extensions == null) {
+                        node.extensions = {};
+                    }
+
+                    node.extensions[NAME] = lightReference;
+                }
+            }
+            resolve(node);
+        });
+    }
+}
+
 _Exporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter));

+ 1 - 1
serializers/src/glTF/2.0/glTFAnimation.ts

@@ -1,7 +1,7 @@
 import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType, IAnimation, INode, IBufferView, IAccessor, IAnimationSampler, IAnimationChannel, AccessorComponentType } from "babylonjs-gltf2interface";
 import { Node } from "babylonjs/node";
 import { Nullable } from "babylonjs/types";
-import { Vector3, Quaternion } from "babylonjs/Maths/math";
+import { Vector3, Quaternion } from "babylonjs/Maths/math.vector";
 import { Tools } from "babylonjs/Misc/tools";
 import { Animation } from "babylonjs/Animations/animation";
 import { TransformNode } from "babylonjs/Meshes/transformNode";

+ 17 - 13
serializers/src/glTF/2.0/glTFExporter.ts

@@ -1,7 +1,8 @@
 import { AccessorType, IBufferView, IAccessor, INode, IScene, IMesh, IMaterial, ITexture, IImage, ISampler, IAnimation, ImageMimeType, IMeshPrimitive, IBuffer, IGLTF, MeshPrimitiveMode, AccessorComponentType, ITextureInfo } from "babylonjs-gltf2interface";
 
 import { FloatArray, Nullable, IndicesArray } from "babylonjs/types";
-import { Viewport, Color3, Vector2, Vector3, Vector4, Quaternion, Epsilon, Matrix } from "babylonjs/Maths/math";
+import { Vector2, Vector3, Vector4, Quaternion, Matrix } from "babylonjs/Maths/math.vector";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Tools } from "babylonjs/Misc/tools";
 import { VertexBuffer } from "babylonjs/Meshes/buffer";
 import { Node } from "babylonjs/node";
@@ -24,6 +25,8 @@ import { IExportOptions } from "./glTFSerializer";
 import { _GLTFUtilities } from "./glTFUtilities";
 import { GLTFData } from "./glTFData";
 import { _GLTFAnimation } from "./glTFAnimation";
+import { Viewport } from 'babylonjs/Maths/math.viewport';
+import { Epsilon } from 'babylonjs/Maths/math.constants';
 
 /**
  * Utility interface for storing vertex attribute data
@@ -67,7 +70,7 @@ export class _Exporter {
     /**
      * Stores all the generated nodes, which contains transform and/or mesh information per node
      */
-    private _nodes: INode[];
+    public _nodes: INode[];
     /**
      * Stores all the generated glTF scenes, which stores multiple node hierarchies
      */
@@ -119,7 +122,7 @@ export class _Exporter {
     /**
      * Stores a map of the unique id of a node to its index in the node array
      */
-    private _nodeMap: { [key: number]: number };
+    public _nodeMap: { [key: number]: number };
 
     /**
      * Specifies if the source Babylon scene was left handed, and needed conversion.
@@ -152,7 +155,7 @@ export class _Exporter {
     private static _ExtensionNames = new Array<string>();
     private static _ExtensionFactories: { [name: string]: (exporter: _Exporter) => IGLTFExporterExtensionV2 } = {};
 
-    private _applyExtension<T>(node: T, extensions: IGLTFExporterExtensionV2[], index: number, actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
+    private _applyExtension<T>(node: Nullable<T>, extensions: IGLTFExporterExtensionV2[], index: number, actionAsync: (extension: IGLTFExporterExtensionV2, node: Nullable<T>) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
         if (index >= extensions.length) {
             return Promise.resolve(node);
         }
@@ -163,10 +166,10 @@ export class _Exporter {
             return this._applyExtension(node, extensions, index + 1, actionAsync);
         }
 
-        return currentPromise.then((newNode) => this._applyExtension(newNode || node, extensions, index + 1, actionAsync));
+        return currentPromise.then((newNode) => this._applyExtension(newNode, extensions, index + 1, actionAsync));
     }
 
-    private _applyExtensions<T>(node: T, actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
+    private _applyExtensions<T>(node: Nullable<T>, actionAsync: (extension: IGLTFExporterExtensionV2, node: Nullable<T>) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
         var extensions: IGLTFExporterExtensionV2[] = [];
         for (const name of _Exporter._ExtensionNames) {
             extensions.push(this._extensions[name]);
@@ -175,7 +178,7 @@ export class _Exporter {
         return this._applyExtension(node, extensions, 0, actionAsync);
     }
 
-    public _extensionsPreExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise<Nullable<BaseTexture>> {
+    public _extensionsPreExportTextureAsync(context: string, babylonTexture: Nullable<Texture>, mimeType: ImageMimeType): Promise<Nullable<BaseTexture>> {
         return this._applyExtensions(babylonTexture, (extension, node) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, node, mimeType));
     }
 
@@ -183,11 +186,11 @@ export class _Exporter {
         return this._applyExtensions(meshPrimitive, (extension, node) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, node, babylonSubMesh, binaryWriter));
     }
 
-    public _extensionsPostExportNodeAsync(context: string, node: INode, babylonNode: Node): Promise<Nullable<INode>> {
-        return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode));
+    public _extensionsPostExportNodeAsync(context: string, node: Nullable<INode>, babylonNode: Node, nodeMap?: {[key: number]: number}): Promise<Nullable<INode>> {
+        return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap));
     }
 
-    public _extensionsPostExportMaterialAsync(context: string, material: IMaterial, babylonMaterial: Material): Promise<Nullable<IMaterial>> {
+    public _extensionsPostExportMaterialAsync(context: string, material: Nullable<IMaterial>, babylonMaterial: Material): Promise<Nullable<IMaterial>> {
         return this._applyExtensions(material, (extension, node) => extension.postExportMaterialAsync && extension.postExportMaterialAsync(context, node, babylonMaterial));
     }
 
@@ -1477,8 +1480,8 @@ export class _Exporter {
             if (!this._options.shouldExportNode || this._options.shouldExportNode(babylonNode)) {
                 promiseChain = promiseChain.then(() => {
                     let convertToRightHandedSystem = this._convertToRightHandedSystemMap[babylonNode.uniqueId];
-                    return this.createNodeAsync(babylonNode, binaryWriter, convertToRightHandedSystem).then((node) => {
-                        const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode);
+                    return this.createNodeAsync(babylonNode, binaryWriter, convertToRightHandedSystem, nodeMap).then((node) => {
+                        const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode, nodeMap);
                         if (promise == null) {
                             Tools.Warn(`Not exporting node ${babylonNode.name}`);
                             return Promise.resolve();
@@ -1528,9 +1531,10 @@ export class _Exporter {
      * @param babylonMesh Source Babylon mesh
      * @param binaryWriter Buffer for storing geometry data
      * @param convertToRightHandedSystem Converts the values to right-handed
+     * @param nodeMap Node mapping of unique id to glTF node index
      * @returns glTF node
      */
-    private createNodeAsync(babylonNode: Node, binaryWriter: _BinaryWriter, convertToRightHandedSystem: boolean): Promise<INode> {
+    private createNodeAsync(babylonNode: Node, binaryWriter: _BinaryWriter, convertToRightHandedSystem: boolean, nodeMap?: {[key: number]: number}): Promise<INode> {
         return Promise.resolve().then(() => {
             // create node to hold translation/rotation/scale and the mesh
             const node: INode = {};

+ 5 - 4
serializers/src/glTF/2.0/glTFExporterExtension.ts

@@ -1,5 +1,6 @@
 import { ImageMimeType, IMeshPrimitive, INode, IMaterial, ITextureInfo } from "babylonjs-gltf2interface";
 import { Node } from "babylonjs/node";
+import { Nullable } from "babylonjs/types";
 
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { SubMesh } from "babylonjs/Meshes/subMesh";
@@ -25,7 +26,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param mimeType The mime-type of the generated image
      * @returns A promise that resolves with the exported texture
      */
-    preExportTextureAsync?(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise<Texture>;
+    preExportTextureAsync?(context: string, babylonTexture: Nullable<Texture>, mimeType: ImageMimeType): Promise<Texture>;
 
     /**
      * Define this method to get notified when a texture info is created
@@ -43,7 +44,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param binaryWriter glTF serializer binary writer instance
      * @returns nullable IMeshPrimitive promise
      */
-    postExportMeshPrimitiveAsync?(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Promise<IMeshPrimitive>;
+    postExportMeshPrimitiveAsync?(context: string, meshPrimitive: Nullable<IMeshPrimitive>, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Promise<IMeshPrimitive>;
 
     /**
      * Define this method to modify the default behavior when exporting a node
@@ -52,7 +53,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param babylonNode BabylonJS node
      * @returns nullable INode promise
      */
-    postExportNodeAsync?(context: string, node: INode, babylonNode: Node): Promise<INode>;
+    postExportNodeAsync?(context: string, node: Nullable<INode>, babylonNode: Node, nodeMap?: {[key: number]: number}): Promise<Nullable<INode>>;
 
     /**
      * Define this method to modify the default behavior when exporting a material
@@ -60,7 +61,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param babylonMaterial BabylonJS material
      * @returns nullable IMaterial promise
      */
-    postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise<IMaterial>;
+    postExportMaterialAsync?(context: string, node: Nullable<IMaterial>, babylonMaterial: Material): Promise<IMaterial>;
 
     /**
      * Define this method to return additional textures to export from a material

+ 2 - 1
serializers/src/glTF/2.0/glTFMaterialExporter.ts

@@ -1,7 +1,8 @@
 import { ITextureInfo, ImageMimeType, IMaterial, IMaterialPbrMetallicRoughness, MaterialAlphaMode, IMaterialOcclusionTextureInfo, ISampler, TextureMagFilter, TextureMinFilter, TextureWrapMode, ITexture, IImage } from "babylonjs-gltf2interface";
 
 import { Nullable } from "babylonjs/types";
-import { Vector2, Color3 } from "babylonjs/Maths/math";
+import { Vector2 } from "babylonjs/Maths/math.vector";
+import { Color3 } from "babylonjs/Maths/math.color";
 import { Scalar } from "babylonjs/Maths/math.scalar";
 import { Tools } from "babylonjs/Misc/tools";
 import { TextureTools } from "babylonjs/Misc/textureTools";

+ 1 - 1
serializers/src/glTF/2.0/glTFUtilities.ts

@@ -1,7 +1,7 @@
 import { IBufferView, AccessorType, AccessorComponentType, IAccessor } from "babylonjs-gltf2interface";
 
 import { FloatArray, Nullable } from "babylonjs/types";
-import { Vector3, Vector4, Quaternion } from "babylonjs/Maths/math";
+import { Vector3, Vector4, Quaternion } from "babylonjs/Maths/math.vector";
 
 /**
  * @hidden

+ 1 - 1
serializers/src/stl/stlSerializer.ts

@@ -1,6 +1,6 @@
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { VertexBuffer } from "babylonjs/Meshes/buffer";
-import { Vector3 } from "babylonjs/Maths/math";
+import { Vector3 } from "babylonjs/Maths/math.vector";
 
 /**
 * Class for generating STL data from a Babylon scene.

+ 19 - 19
src/XR/features/WebXRAbstractFeature.ts

@@ -9,20 +9,24 @@ import { WebXRSessionManager } from '../webXRSessionManager';
  * Note that since the features manager is using the `IWebXRFeature` you are in no way obligated to use this class
  */
 export abstract class WebXRAbstractFeature implements IWebXRFeature {
+    private _attached: boolean = false;
+    private _removeOnDetach: {
+        observer: Nullable<Observer<any>>;
+        observable: Observable<any>;
+    }[] = [];
+
+    /**
+     * Should auto-attach be disabled?
+     */
+    public disableAutoAttach: boolean = false;
 
     /**
-     * Construct a new (abstract) webxr feature
+     * Construct a new (abstract) WebXR feature
      * @param _xrSessionManager the xr session manager for this feature
      */
     constructor(protected _xrSessionManager: WebXRSessionManager) {
     }
 
-    private _attached: boolean = false;
-    private _removeOnDetach: {
-        observer: Nullable<Observer<any>>;
-        observable: Observable<any>;
-    }[] = [];
-
     /**
      * Is this feature attached
      */
@@ -31,11 +35,6 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
     }
 
     /**
-     * Should auto-attach be disabled?
-     */
-    public disableAutoAttach: boolean = false;
-
-    /**
      * attach this feature
      *
      * @param force should attachment be forced (even when already attached)
@@ -74,6 +73,7 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
         });
         return true;
     }
+
     /**
      * Dispose this feature and all of the resources attached
      */
@@ -82,13 +82,6 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
     }
 
     /**
-     * Code in this function will be executed on each xrFrame received from the browser.
-     * This function will not execute after the feature is detached.
-     * @param _xrFrame the current frame
-     */
-    protected abstract _onXRFrame(_xrFrame: XRFrame): void;
-
-    /**
      * This is used to register callbacks that will automatically be removed when detach is called.
      * @param observable the observable to which the observer will be attached
      * @param callback the callback to register
@@ -99,4 +92,11 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
             observer: observable.add(callback)
         });
     }
+
+    /**
+     * Code in this function will be executed on each xrFrame received from the browser.
+     * This function will not execute after the feature is detached.
+     * @param _xrFrame the current frame
+     */
+    protected abstract _onXRFrame(_xrFrame: XRFrame): void;
 }

+ 80 - 83
src/XR/features/WebXRAnchorSystem.ts

@@ -12,18 +12,18 @@ import { WebXRAbstractFeature } from './WebXRAbstractFeature';
  */
 export interface IWebXRAnchorSystemOptions {
     /**
-     * a node that will be used to convert local to world coordinates
+     * Should a new anchor be added every time a select event is triggered
      */
-    worldParentNode?: TransformNode;
+    addAnchorOnSelect?: boolean;
     /**
      * should the anchor system use plane detection.
      * If set to true, the plane-detection feature should be set using setPlaneDetector
      */
     usePlaneDetection?: boolean;
     /**
-     * Should a new anchor be added every time a select event is triggered
+     * a node that will be used to convert local to world coordinates
      */
-    addAnchorOnSelect?: boolean;
+    worldParentNode?: TransformNode;
 }
 
 /**
@@ -35,13 +35,13 @@ export interface IWebXRAnchor {
      */
     id: number;
     /**
-     * The native anchor object
-     */
-    xrAnchor: XRAnchor;
-    /**
      * Transformation matrix to apply to an object attached to this anchor
      */
     transformationMatrix: Matrix;
+    /**
+     * The native anchor object
+     */
+    xrAnchor: XRAnchor;
 }
 
 let anchorIdProvider = 0;
@@ -53,6 +53,34 @@ let anchorIdProvider = 0;
  * For further information see https://github.com/immersive-web/anchors/
  */
 export class WebXRAnchorSystem extends WebXRAbstractFeature {
+    private _enabled: boolean = false;
+    private _hitTestModule: WebXRHitTestLegacy;
+    private _lastFrameDetected: XRAnchorSet = new Set();
+    private _onSelect = (event: XRInputSourceEvent) => {
+        if (!this._options.addAnchorOnSelect) {
+            return;
+        }
+        const onResults = (results: XRHitResult[]) => {
+            if (results.length) {
+                const hitResult = results[0];
+                const transform = new XRRigidTransform(hitResult.hitMatrix);
+                // find the plane on which to add.
+                this.addAnchorAtRigidTransformation(transform);
+            }
+        };
+
+        // avoid the hit-test, if the hit-test module is defined
+        if (this._hitTestModule && !this._hitTestModule.options.testOnPointerDownOnly) {
+            onResults(this._hitTestModule.lastNativeXRHitResults);
+        }
+        WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace).then(onResults);
+
+        // API will soon change, will need to use the plane
+        this._planeDetector;
+    }
+
+    private _planeDetector: WebXRPlaneDetector;
+    private _trackedAnchors: Array<IWebXRAnchor> = [];
 
     /**
      * The module's name
@@ -61,7 +89,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
+     * This number does not correspond to the WebXR specs version
      */
     public static readonly Version = 1;
 
@@ -70,21 +98,14 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
      */
     public onAnchorAddedObservable: Observable<IWebXRAnchor> = new Observable();
     /**
+     * Observers registered here will be executed when an anchor was removed from the session
+     */
+    public onAnchorRemovedObservable: Observable<IWebXRAnchor> = new Observable();
+    /**
      * Observers registered here will be executed when an existing anchor updates
      * This can execute N times every frame
      */
     public onAnchorUpdatedObservable: Observable<IWebXRAnchor> = new Observable();
-    /**
-     * Observers registered here will be executed when an anchor was removed from the session
-     */
-    public onAnchorRemovedObservable: Observable<IWebXRAnchor> = new Observable();
-
-    private _planeDetector: WebXRPlaneDetector;
-    private _hitTestModule: WebXRHitTestLegacy;
-
-    private _enabled: boolean = false;
-    private _trackedAnchors: Array<IWebXRAnchor> = [];
-    private _lastFrameDetected: XRAnchorSet = new Set();
 
     /**
      * constructs a new anchor system
@@ -96,21 +117,15 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
     }
 
     /**
-     * set the plane detector to use in order to create anchors from frames
-     * @param planeDetector the plane-detector module to use
-     * @param enable enable plane-anchors. default is true
-     */
-    public setPlaneDetector(planeDetector: WebXRPlaneDetector, enable: boolean = true) {
-        this._planeDetector = planeDetector;
-        this._options.usePlaneDetection = enable;
-    }
-
-    /**
-     * If set, it will improve performance by using the current hit-test results instead of executing a new hit-test
-     * @param hitTestModule the hit-test module to use.
+     * Add anchor at a specific XR point.
+     *
+     * @param xrRigidTransformation xr-coordinates where a new anchor should be added
+     * @param anchorCreator the object o use to create an anchor with. either a session or a plane
+     * @returns a promise the fulfills when the anchor was created
      */
-    public setHitTestModule(hitTestModule: WebXRHitTestLegacy) {
-        this._hitTestModule = hitTestModule;
+    public addAnchorAtRigidTransformation(xrRigidTransformation: XRRigidTransform, anchorCreator?: XRAnchorCreator): Promise<XRAnchor> {
+        const creator = anchorCreator || this._xrSessionManager.session;
+        return creator.createAnchor(xrRigidTransformation, this._xrSessionManager.referenceSpace);
     }
 
     /**
@@ -119,7 +134,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    attach(): boolean {
+    public attach(): boolean {
         if (!super.attach()) {
             return false;
         }
@@ -135,7 +150,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    detach(): boolean {
+    public detach(): boolean {
         if (!super.detach()) {
             return false;
         }
@@ -148,13 +163,31 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
     /**
      * Dispose this feature and all of the resources attached
      */
-    dispose(): void {
+    public dispose(): void {
         super.dispose();
         this.onAnchorAddedObservable.clear();
         this.onAnchorRemovedObservable.clear();
         this.onAnchorUpdatedObservable.clear();
     }
 
+    /**
+     * If set, it will improve performance by using the current hit-test results instead of executing a new hit-test
+     * @param hitTestModule the hit-test module to use.
+     */
+    public setHitTestModule(hitTestModule: WebXRHitTestLegacy) {
+        this._hitTestModule = hitTestModule;
+    }
+
+    /**
+     * set the plane detector to use in order to create anchors from frames
+     * @param planeDetector the plane-detector module to use
+     * @param enable enable plane-anchors. default is true
+     */
+    public setPlaneDetector(planeDetector: WebXRPlaneDetector, enable: boolean = true) {
+        this._planeDetector = planeDetector;
+        this._options.usePlaneDetection = enable;
+    }
+
     protected _onXRFrame(frame: XRFrame) {
         if (!this.attached || !this._enabled || !frame) { return; }
 
@@ -189,39 +222,17 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
         }
     }
 
-    private _onSelect = (event: XRInputSourceEvent) => {
-        if (!this._options.addAnchorOnSelect) {
-            return;
-        }
-        const onResults = (results: XRHitResult[]) => {
-            if (results.length) {
-                const hitResult = results[0];
-                const transform = new XRRigidTransform(hitResult.hitMatrix);
-                // find the plane on which to add.
-                this.addAnchorAtRigidTransformation(transform);
-            }
-        };
-
-        // avoid the hit-test, if the hit-test module is defined
-        if (this._hitTestModule && !this._hitTestModule.options.testOnPointerDownOnly) {
-            onResults(this._hitTestModule.lastNativeXRHitResults);
-        }
-        WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace).then(onResults);
-
-        // API will soon change, will need to use the plane
-        this._planeDetector;
-    }
-
     /**
-     * Add anchor at a specific XR point.
-     *
-     * @param xrRigidTransformation xr-coordinates where a new anchor should be added
-     * @param anchorCreator the object o use to create an anchor with. either a session or a plane
-     * @returns a promise the fulfills when the anchor was created
+     * avoiding using Array.find for global support.
+     * @param xrAnchor the plane to find in the array
      */
-    public addAnchorAtRigidTransformation(xrRigidTransformation: XRRigidTransform, anchorCreator?: XRAnchorCreator): Promise<XRAnchor> {
-        const creator = anchorCreator || this._xrSessionManager.session;
-        return creator.createAnchor(xrRigidTransformation, this._xrSessionManager.referenceSpace);
+    private _findIndexInAnchorArray(xrAnchor: XRAnchor) {
+        for (let i = 0; i < this._trackedAnchors.length; ++i) {
+            if (this._trackedAnchors[i].xrAnchor === xrAnchor) {
+                return i;
+            }
+        }
+        return -1;
     }
 
     private _updateAnchorWithXRFrame(xrAnchor: XRAnchor, anchor: Partial<IWebXRAnchor>, xrFrame: XRFrame): IWebXRAnchor {
@@ -243,23 +254,9 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
 
         return <IWebXRAnchor>anchor;
     }
-
-    /**
-     * avoiding using Array.find for global support.
-     * @param xrAnchor the plane to find in the array
-     */
-    private _findIndexInAnchorArray(xrAnchor: XRAnchor) {
-        for (let i = 0; i < this._trackedAnchors.length; ++i) {
-            if (this._trackedAnchors[i].xrAnchor === xrAnchor) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
 }
 
-//register the plugin
+// register the plugin
 // WebXRFeaturesManager.AddWebXRFeature(WebXRAnchorSystem.Name, (xrSessionManager, options) => {
 //     return () => new WebXRAnchorSystem(xrSessionManager, options);
 // }, WebXRAnchorSystem.Version);

+ 19 - 20
src/XR/features/WebXRBackgroundRemover.ts

@@ -9,9 +9,9 @@ import { WebXRAbstractFeature } from './WebXRAbstractFeature';
  */
 export interface IWebXRBackgroundRemoverOptions {
     /**
-     * don't disable the environment helper
+     * Further background meshes to disable when entering AR
      */
-    ignoreEnvironmentHelper?: boolean;
+    backgroundMeshes?: AbstractMesh[];
     /**
      * flags to configure the removal of the environment helper.
      * If not set, the entire background will be removed. If set, flags should be set as well.
@@ -27,16 +27,15 @@ export interface IWebXRBackgroundRemoverOptions {
         ground?: boolean;
     };
     /**
-     * Further background meshes to disable when entering AR
+     * don't disable the environment helper
      */
-    backgroundMeshes?: AbstractMesh[];
+    ignoreEnvironmentHelper?: boolean;
 }
 
 /**
  * A module that will automatically disable background meshes when entering AR and will enable them when leaving AR.
  */
 export class WebXRBackgroundRemover extends WebXRAbstractFeature {
-
     /**
      * The module's name
      */
@@ -44,7 +43,7 @@ export class WebXRBackgroundRemover extends WebXRAbstractFeature {
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
+     * This number does not correspond to the WebXR specs version
      */
     public static readonly Version = 1;
 
@@ -72,7 +71,7 @@ export class WebXRBackgroundRemover extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    attach(): boolean {
+    public attach(): boolean {
         this._setBackgroundState(false);
         return super.attach();
     }
@@ -83,11 +82,23 @@ export class WebXRBackgroundRemover extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    detach(): boolean {
+    public detach(): boolean {
         this._setBackgroundState(true);
         return super.detach();
     }
 
+    /**
+     * Dispose this feature and all of the resources attached
+     */
+    public dispose(): void {
+        super.dispose();
+        this.onBackgroundStateChangedObservable.clear();
+    }
+
+    protected _onXRFrame(_xrFrame: XRFrame) {
+        // no-op
+    }
+
     private _setBackgroundState(newState: boolean) {
         const scene = this._xrSessionManager.scene;
         if (!this.options.ignoreEnvironmentHelper) {
@@ -118,18 +129,6 @@ export class WebXRBackgroundRemover extends WebXRAbstractFeature {
 
         this.onBackgroundStateChangedObservable.notifyObservers(newState);
     }
-
-    /**
-     * Dispose this feature and all of the resources attached
-     */
-    dispose(): void {
-        super.dispose();
-        this.onBackgroundStateChangedObservable.clear();
-    }
-
-    protected _onXRFrame(_xrFrame: XRFrame) {
-        // no-op
-    }
 }
 
 //register the plugin

+ 134 - 142
src/XR/features/WebXRControllerPhysics.ts

@@ -15,22 +15,17 @@ import { Nullable } from '../../types';
  */
 export class IWebXRControllerPhysicsOptions {
     /**
-     * the xr input to use with this pointer selection
+     * Should the headset get its own impostor
      */
-    xrInput: WebXRInput;
+    enableHeadsetImpostor?: boolean;
     /**
-     * The physics properties of the future impostors
+     * Optional parameters for the headset impostor
      */
-    physicsProperties?: {
-        /**
-         * If set to true, a mesh impostor will be created when the controller mesh was loaded
-         * Note that this requires a physics engine that supports mesh impostors!
-         */
-        useControllerMesh?: boolean;
+    headsetImpostorParams?: {
         /**
          * The type of impostor to create. Default is sphere
          */
-        impostorType?: number;
+        impostorType: number;
         /**
          * the size of the impostor. Defaults to 10cm
          */
@@ -44,20 +39,19 @@ export class IWebXRControllerPhysicsOptions {
          */
         restitution?: number;
     };
-
-    /**
-     * Should the headset get its own impostor
-     */
-    enableHeadsetImpostor?: boolean;
-
     /**
-     * Optional parameters for the headset impostor
+     * The physics properties of the future impostors
      */
-    headsetImpostorParams?: {
+    physicsProperties?: {
+        /**
+         * If set to true, a mesh impostor will be created when the controller mesh was loaded
+         * Note that this requires a physics engine that supports mesh impostors!
+         */
+        useControllerMesh?: boolean;
         /**
          * The type of impostor to create. Default is sphere
          */
-        impostorType: number;
+        impostorType?: number;
         /**
          * the size of the impostor. Defaults to 10cm
          */
@@ -71,6 +65,10 @@ export class IWebXRControllerPhysicsOptions {
          */
         restitution?: number;
     };
+    /**
+     * the xr input to use with this pointer selection
+     */
+    public xrInput: WebXRInput;
 }
 
 /**
@@ -78,20 +76,56 @@ export class IWebXRControllerPhysicsOptions {
  * including naive calculation of their linear and angular velocity
  */
 export class WebXRControllerPhysics extends WebXRAbstractFeature {
-
-    /**
-     * The module's name
-     */
-    public static readonly Name = WebXRFeatureName.PHYSICS_CONTROLLERS;
-    /**
-     * The (Babylon) version of this module.
-     * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
-     */
-    public static readonly Version = 1;
-
-    private _lastTimestamp: number = 0;
-    private _delta: number = 0;
+    private _attachController = (xrController: WebXRInputSource
+    ) => {
+        if (this._controllers[xrController.uniqueId]) {
+            // already attached
+            return;
+        }
+        if (!this._xrSessionManager.scene.isPhysicsEnabled()) {
+            Logger.Warn("physics engine not enabled, skipped. Please add this controller manually.");
+        }
+        if (this._options.physicsProperties!.useControllerMesh) {
+            xrController.onMotionControllerInitObservable.addOnce((motionController) => {
+                motionController.onModelLoadedObservable.addOnce(() => {
+                    const impostor = new PhysicsImpostor(motionController.rootMesh!, PhysicsImpostor.MeshImpostor, {
+                        mass: 0,
+                        ...this._options.physicsProperties
+                    });
+                    const controllerMesh = xrController.grip || xrController.pointer;
+                    this._controllers[xrController.uniqueId] = {
+                        xrController,
+                        impostor,
+                        oldPos: controllerMesh.position.clone(),
+                        oldRotation: controllerMesh.rotationQuaternion!.clone()
+                    };
+                });
+            });
+        } else {
+            const impostorType: number = this._options.physicsProperties!.impostorType || PhysicsImpostor.SphereImpostor;
+            const impostorSize: number | { width: number, height: number, depth: number } = this._options.physicsProperties!.impostorSize || 0.1;
+            const impostorMesh = SphereBuilder.CreateSphere('impostor-mesh-' + xrController.uniqueId, {
+                diameterX: typeof impostorSize === 'number' ? impostorSize : impostorSize.width,
+                diameterY: typeof impostorSize === 'number' ? impostorSize : impostorSize.height,
+                diameterZ: typeof impostorSize === 'number' ? impostorSize : impostorSize.depth
+            });
+            impostorMesh.isVisible = this._debugMode;
+            impostorMesh.isPickable = false;
+            impostorMesh.rotationQuaternion = new Quaternion();
+            const controllerMesh = xrController.grip || xrController.pointer;
+            impostorMesh.position.copyFrom(controllerMesh.position);
+            impostorMesh.rotationQuaternion!.copyFrom(controllerMesh.rotationQuaternion!);
+            const impostor = new PhysicsImpostor(impostorMesh, impostorType, {
+                mass: 0,
+                ...this._options.physicsProperties
+            });
+            this._controllers[xrController.uniqueId] = {
+                xrController,
+                impostor,
+                impostorMesh
+            };
+        }
+    }
 
     private _controllers: {
         [id: string]: {
@@ -103,12 +137,24 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
             oldRotation?: Quaternion;
         }
     } = {};
-
+    private _debugMode = false;
+    private _delta: number = 0;
     private _headsetImpostor?: PhysicsImpostor;
     private _headsetMesh?: AbstractMesh;
-
-    private _tmpVector: Vector3 = new Vector3();
+    private _lastTimestamp: number = 0;
     private _tmpQuaternion: Quaternion = new Quaternion();
+    private _tmpVector: Vector3 = new Vector3();
+
+    /**
+     * The module's name
+     */
+    public static readonly Name = WebXRFeatureName.PHYSICS_CONTROLLERS;
+    /**
+     * The (Babylon) version of this module.
+     * This is an integer representing the implementation version.
+     * This number does not correspond to the webxr specs version
+     */
+    public static readonly Version = 1;
 
     /**
      * Construct a new Controller Physics Feature
@@ -124,42 +170,25 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
     }
 
     /**
-     * Update the physics properties provided in the constructor
-     * @param newProperties the new properties object
-     */
-    public setPhysicsProperties(newProperties: {
-        impostorType?: number,
-        impostorSize?: number | { width: number, height: number, depth: number },
-        friction?: number,
-        restitution?: number
-    }) {
-        this._options.physicsProperties = {
-            ...this._options.physicsProperties,
-            ...newProperties
-        };
-    }
-
-    /**
-     * Get the physics impostor of a specific controller.
-     * The impostor is not attached to a mesh because a mesh for each controller is not obligatory
-     * @param controller the controller or the controller id of which to get the impostor
-     * @returns the impostor or null
+     * @hidden
+     * enable debugging - will show console outputs and the impostor mesh
      */
-    public getImpostorForController(controller: WebXRInputSource | string): Nullable<PhysicsImpostor> {
-        let id = typeof controller === "string" ? controller : controller.uniqueId;
-        if (this._controllers[id]) {
-            return this._controllers[id].impostor;
-        } else {
-            return null;
-        }
+    public _enablePhysicsDebug() {
+        this._debugMode = true;
+        Object.keys(this._controllers).forEach((controllerId) => {
+            const controllerData = this._controllers[controllerId];
+            if (controllerData.impostorMesh) {
+                controllerData.impostorMesh.isVisible = true;
+            }
+        });
     }
 
     /**
-     * Get the headset impostor, if enabled
-     * @returns the impostor
+     * Manually add a controller (if no xrInput was provided or physics engine was not enabled)
+     * @param xrController the controller to add
      */
-    public getHeadsetImpostor() {
-        return this._headsetImpostor;
+    public addController(xrController: WebXRInputSource) {
+        this._attachController(xrController);
     }
 
     /**
@@ -168,7 +197,7 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    attach(): boolean {
+    public attach(): boolean {
         if (!super.attach()) {
             return false;
         }
@@ -210,7 +239,7 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    detach(): boolean {
+    public detach(): boolean {
         if (!super.detach()) {
             return false;
         }
@@ -227,88 +256,42 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
     }
 
     /**
-     * Manually add a controller (if no xrInput was provided or physics engine was not enabled)
-     * @param xrController the controller to add
+     * Get the headset impostor, if enabled
+     * @returns the impostor
      */
-    public addController(xrController: WebXRInputSource) {
-        this._attachController(xrController);
+    public getHeadsetImpostor() {
+        return this._headsetImpostor;
     }
 
-    private _debugMode = false;
-
     /**
-     * @hidden
-     * enable debugging - will show console outputs and the impostor mesh
+     * Get the physics impostor of a specific controller.
+     * The impostor is not attached to a mesh because a mesh for each controller is not obligatory
+     * @param controller the controller or the controller id of which to get the impostor
+     * @returns the impostor or null
      */
-    public _enablePhysicsDebug() {
-        this._debugMode = true;
-        Object.keys(this._controllers).forEach((controllerId) => {
-            const controllerData = this._controllers[controllerId];
-            if (controllerData.impostorMesh) {
-                controllerData.impostorMesh.isVisible = true;
-            }
-        });
-    }
-
-    private _attachController = (xrController: WebXRInputSource
-    ) => {
-        if (this._controllers[xrController.uniqueId]) {
-            // already attached
-            return;
-        }
-        if (!this._xrSessionManager.scene.isPhysicsEnabled()) {
-            Logger.Warn("physics engine not enabled, skipped. Please add this controller manually.");
-        }
-        if (this._options.physicsProperties!.useControllerMesh) {
-            xrController.onMotionControllerInitObservable.addOnce((motionController) => {
-                motionController.onModelLoadedObservable.addOnce(() => {
-                    const impostor = new PhysicsImpostor(motionController.rootMesh!, PhysicsImpostor.MeshImpostor, {
-                        mass: 0,
-                        ...this._options.physicsProperties
-                    });
-                    const controllerMesh = xrController.grip || xrController.pointer;
-                    this._controllers[xrController.uniqueId] = {
-                        xrController,
-                        impostor,
-                        oldPos: controllerMesh.position.clone(),
-                        oldRotation: controllerMesh.rotationQuaternion!.clone()
-                    };
-                });
-            });
+    public getImpostorForController(controller: WebXRInputSource | string): Nullable<PhysicsImpostor> {
+        let id = typeof controller === "string" ? controller : controller.uniqueId;
+        if (this._controllers[id]) {
+            return this._controllers[id].impostor;
         } else {
-            const impostorType: number = this._options.physicsProperties!.impostorType || PhysicsImpostor.SphereImpostor;
-            const impostorSize: number | { width: number, height: number, depth: number } = this._options.physicsProperties!.impostorSize || 0.1;
-            const impostorMesh = SphereBuilder.CreateSphere('impostor-mesh-' + xrController.uniqueId, {
-                diameterX: typeof impostorSize === 'number' ? impostorSize : impostorSize.width,
-                diameterY: typeof impostorSize === 'number' ? impostorSize : impostorSize.height,
-                diameterZ: typeof impostorSize === 'number' ? impostorSize : impostorSize.depth
-            });
-            impostorMesh.isVisible = this._debugMode;
-            impostorMesh.isPickable = false;
-            impostorMesh.rotationQuaternion = new Quaternion();
-            const controllerMesh = xrController.grip || xrController.pointer;
-            impostorMesh.position.copyFrom(controllerMesh.position);
-            impostorMesh.rotationQuaternion!.copyFrom(controllerMesh.rotationQuaternion!);
-            const impostor = new PhysicsImpostor(impostorMesh, impostorType, {
-                mass: 0,
-                ...this._options.physicsProperties
-            });
-            this._controllers[xrController.uniqueId] = {
-                xrController,
-                impostor,
-                impostorMesh
-            };
+            return null;
         }
     }
 
-    private _detachController(xrControllerUniqueId: string) {
-        const controllerData = this._controllers[xrControllerUniqueId];
-        if (!controllerData) { return; }
-        if (controllerData.impostorMesh) {
-            controllerData.impostorMesh.dispose();
-        }
-        // remove from the map
-        delete this._controllers[xrControllerUniqueId];
+    /**
+     * Update the physics properties provided in the constructor
+     * @param newProperties the new properties object
+     */
+    public setPhysicsProperties(newProperties: {
+        impostorType?: number,
+        impostorSize?: number | { width: number, height: number, depth: number },
+        friction?: number,
+        restitution?: number
+    }) {
+        this._options.physicsProperties = {
+            ...this._options.physicsProperties,
+            ...newProperties
+        };
     }
 
     protected _onXRFrame(_xrFrame: any): void {
@@ -354,6 +337,15 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
         });
     }
 
+    private _detachController(xrControllerUniqueId: string) {
+        const controllerData = this._controllers[xrControllerUniqueId];
+        if (!controllerData) { return; }
+        if (controllerData.impostorMesh) {
+            controllerData.impostorMesh.dispose();
+        }
+        // remove from the map
+        delete this._controllers[xrControllerUniqueId];
+    }
 }
 
 //register the plugin

+ 136 - 149
src/XR/features/WebXRControllerPointerSelection.ts

@@ -23,20 +23,9 @@ import { UtilityLayerRenderer } from '../../Rendering/utilityLayerRenderer';
  */
 export interface IWebXRControllerPointerSelectionOptions {
     /**
-     * the xr input to use with this pointer selection
-     */
-    xrInput: WebXRInput;
-    /**
-     * Different button type to use instead of the main component
-     */
-    overrideButtonId?: string;
-    /**
-     * The amount of time in miliseconds it takes between pick found something to a pointer down event.
-     * Used in gaze modes. Tracked pointer uses the trigger, screen uses touch events
-     * 3000 means 3 seconds between pointing at something and selecting it
+     * if provided, this scene will be used to render meshes.
      */
-    timeToSelect?: number;
-
+    customUtilityLayerScene?: Scene;
     /**
      * Disable the pointer up event when the xr controller in screen and gaze mode is disposed (meaning - when the user removed the finger from the screen)
      * If not disabled, the last picked point will be used to execute a pointer up event
@@ -44,39 +33,90 @@ export interface IWebXRControllerPointerSelectionOptions {
      * Used in screen and gaze target ray mode only
      */
     disablePointerUpOnTouchOut: boolean;
-
     /**
      * For gaze mode (time to select instead of press)
      */
     forceGazeMode: boolean;
-
     /**
      * Factor to be applied to the pointer-moved function in the gaze mode. How sensitive should the gaze mode be when checking if the pointer moved
      * to start a new countdown to the pointer down event.
      * Defaults to 1.
      */
     gazeModePointerMovedFactor?: number;
-
     /**
-     * Should meshes created here be added to a utility layer or the main scene
-     */
-    useUtilityLayer?: boolean;
-
-    /**
-     * if provided, this scene will be used to render meshes.
+     * Different button type to use instead of the main component
      */
-    customUtilityLayerScene?: Scene;
-
+    overrideButtonId?: string;
     /**
      *  use this rendering group id for the meshes (optional)
      */
     renderingGroupId?: number;
+    /**
+     * The amount of time in milliseconds it takes between pick found something to a pointer down event.
+     * Used in gaze modes. Tracked pointer uses the trigger, screen uses touch events
+     * 3000 means 3 seconds between pointing at something and selecting it
+     */
+    timeToSelect?: number;
+    /**
+     * Should meshes created here be added to a utility layer or the main scene
+     */
+    useUtilityLayer?: boolean;
+    /**
+     * the xr input to use with this pointer selection
+     */
+    xrInput: WebXRInput;
 }
 
 /**
  * A module that will enable pointer selection for motion controllers of XR Input Sources
  */
 export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
+    private static _idCounter = 0;
+
+    private _attachController = (xrController: WebXRInputSource) => {
+        if (this._controllers[xrController.uniqueId]) {
+            // already attached
+            return;
+        }
+        // only support tracker pointer
+        const { laserPointer, selectionMesh } = this._generateNewMeshPair(xrController);
+
+        // get two new meshes
+        this._controllers[xrController.uniqueId] = {
+            xrController,
+            laserPointer,
+            selectionMesh,
+            meshUnderPointer: null,
+            pick: null,
+            tmpRay: new Ray(new Vector3(), new Vector3()),
+            id: WebXRControllerPointerSelection._idCounter++
+        };
+        switch (xrController.inputSource.targetRayMode) {
+            case "tracked-pointer":
+                return this._attachTrackedPointerRayMode(xrController);
+            case "gaze":
+                return this._attachGazeMode(xrController);
+            case "screen":
+                return this._attachScreenRayMode(xrController);
+        }
+    }
+
+    private _controllers: {
+        [controllerUniqueId: string]: {
+            xrController: WebXRInputSource;
+            selectionComponent?: WebXRControllerComponent;
+            onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
+            onFrameObserver?: Nullable<Observer<XRFrame>>;
+            laserPointer: AbstractMesh;
+            selectionMesh: AbstractMesh;
+            meshUnderPointer: Nullable<AbstractMesh>;
+            pick: Nullable<PickingInfo>;
+            id: number;
+            tmpRay: Ray;
+        };
+    } = {};
+    private _scene: Scene;
+    private _tmpVectorForPickCompare = new Vector3();
 
     /**
      * The module's name
@@ -85,27 +125,18 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
+     * This number does not correspond to the WebXR specs version
      */
     public static readonly Version = 1;
 
     /**
-     * This color will be set to the laser pointer when selection is triggered
-     */
-    public laserPointerPickedColor: Color3 = new Color3(0.9, 0.9, 0.9);
-    /**
-     * This color will be applied to the selection ring when selection is triggered
-     */
-    public selectionMeshPickedColor: Color3 = new Color3(0.3, 0.3, 1.0);
-    /**
-     * default color of the selection ring
+     * Disable lighting on the laser pointer (so it will always be visible)
      */
-    public selectionMeshDefaultColor: Color3 = new Color3(0.8, 0.8, 0.8);
+    public disablePointerLighting: boolean = true;
     /**
-     * Default color of the laser pointer
+     * Disable lighting on the selection mesh (so it will always be visible)
      */
-    public lasterPointerDefaultColor: Color3 = new Color3(0.7, 0.7, 0.7);
-
+    public disableSelectionMeshLighting: boolean = true;
     /**
      * Should the laser pointer be displayed
      */
@@ -114,35 +145,22 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
      * Should the selection mesh be displayed (The ring at the end of the laser pointer)
      */
     public displaySelectionMesh: boolean = true;
-
     /**
-     * Disable lighting on the laser pointer (so it will always be visible)
+     * This color will be set to the laser pointer when selection is triggered
      */
-    public disablePointerLighting: boolean = true;
-
+    public laserPointerPickedColor: Color3 = new Color3(0.9, 0.9, 0.9);
     /**
-     * Disable lighting on the selection mesh (so it will always be visible)
+     * Default color of the laser pointer
      */
-    public disableSelectionMeshLighting: boolean = true;
-
-    private static _idCounter = 0;
-
-    private _controllers: {
-        [controllerUniqueId: string]: {
-            xrController: WebXRInputSource;
-            selectionComponent?: WebXRControllerComponent;
-            onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
-            onFrameObserver?: Nullable<Observer<XRFrame>>;
-            laserPointer: AbstractMesh;
-            selectionMesh: AbstractMesh;
-            meshUnderPointer: Nullable<AbstractMesh>;
-            pick: Nullable<PickingInfo>;
-            id: number;
-            tmpRay: Ray;
-        };
-    } = {};
-
-    private _scene: Scene;
+    public lasterPointerDefaultColor: Color3 = new Color3(0.7, 0.7, 0.7);
+    /**
+     * default color of the selection ring
+     */
+    public selectionMeshDefaultColor: Color3 = new Color3(0.8, 0.8, 0.8);
+    /**
+     * This color will be applied to the selection ring when selection is triggered
+     */
+    public selectionMeshPickedColor: Color3 = new Color3(0.3, 0.3, 1.0);
 
     /**
      * constructs a new background remover module
@@ -160,7 +178,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    attach(): boolean {
+    public attach(): boolean {
         if (!super.attach()) {
             return false;
         }
@@ -181,7 +199,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    detach(): boolean {
+    public detach(): boolean {
         if (!super.detach()) {
             return false;
         }
@@ -194,6 +212,20 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
     }
 
     /**
+     * Will get the mesh under a specific pointer.
+     * `scene.meshUnderPointer` will only return one mesh - either left or right.
+     * @param controllerId the controllerId to check
+     * @returns The mesh under pointer or null if no mesh is under the pointer
+     */
+    public getMeshUnderPointer(controllerId: string): Nullable<AbstractMesh> {
+        if (this._controllers[controllerId]) {
+            return this._controllers[controllerId].meshUnderPointer;
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Get the xr controller that correlates to the pointer id in the pointer event
      *
      * @param id the pointer id to search for
@@ -210,20 +242,6 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
         return null;
     }
 
-    /**
-     * Will get the mesh under a specific pointer.
-     * `scene.meshUnderPointer` will only return one mesh - either left or right.
-     * @param controllerId the controllerId to check
-     * @returns The mesh under pointer or null if no mesh is under the pointer
-     */
-    public getMeshUnderPointer(controllerId: string): Nullable<AbstractMesh> {
-        if (this._controllers[controllerId]) {
-            return this._controllers[controllerId].meshUnderPointer;
-        } else {
-            return null;
-        }
-    }
-
     protected _onXRFrame(_xrFrame: XRFrame) {
         Object.keys(this._controllers).forEach((id) => {
             const controllerData = this._controllers[id];
@@ -263,56 +281,6 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
         });
     }
 
-    private _attachController = (xrController: WebXRInputSource) => {
-        if (this._controllers[xrController.uniqueId]) {
-            // already attached
-            return;
-        }
-        // only support tracker pointer
-        const { laserPointer, selectionMesh } = this._generateNewMeshPair(xrController);
-
-        // get two new meshes
-        this._controllers[xrController.uniqueId] = {
-            xrController,
-            laserPointer,
-            selectionMesh,
-            meshUnderPointer: null,
-            pick: null,
-            tmpRay: new Ray(new Vector3(), new Vector3()),
-            id: WebXRControllerPointerSelection._idCounter++
-        };
-        switch (xrController.inputSource.targetRayMode) {
-            case "tracked-pointer":
-                return this._attachTrackedPointerRayMode(xrController);
-            case "gaze":
-                return this._attachGazeMode(xrController);
-            case "screen":
-                return this._attachScreenRayMode(xrController);
-        }
-    }
-
-    private _attachScreenRayMode(xrController: WebXRInputSource) {
-        const controllerData = this._controllers[xrController.uniqueId];
-        let downTriggered = false;
-        controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
-            if (!controllerData.pick || (this._options.disablePointerUpOnTouchOut && downTriggered)) { return; }
-            if (!downTriggered) {
-                this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
-                downTriggered = true;
-                if (this._options.disablePointerUpOnTouchOut) {
-                    this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
-                }
-            } else {
-                this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
-            }
-        });
-        xrController.onDisposeObservable.addOnce(() => {
-            if (controllerData.pick && downTriggered && !this._options.disablePointerUpOnTouchOut) {
-                this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
-            }
-        });
-    }
-
     private _attachGazeMode(xrController: WebXRInputSource) {
         const controllerData = this._controllers[xrController.uniqueId];
         // attached when touched, detaches when raised
@@ -380,19 +348,27 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
             discMesh.dispose();
         });
     }
-    private _tmpVectorForPickCompare = new Vector3();
-
-    private _pickingMoved(oldPick: PickingInfo, newPick: PickingInfo) {
-        if (!oldPick.hit || !newPick.hit) { return true; }
-        if (!oldPick.pickedMesh || !oldPick.pickedPoint || !newPick.pickedMesh || !newPick.pickedPoint) { return true; }
-        if (oldPick.pickedMesh !== newPick.pickedMesh) { return true; }
-        oldPick.pickedPoint?.subtractToRef(newPick.pickedPoint, this._tmpVectorForPickCompare);
-        this._tmpVectorForPickCompare.set(Math.abs(this._tmpVectorForPickCompare.x), Math.abs(this._tmpVectorForPickCompare.y), Math.abs(this._tmpVectorForPickCompare.z));
-        const delta = (this._options.gazeModePointerMovedFactor || 1) * 0.01 / newPick.distance;
-        const length = this._tmpVectorForPickCompare.length();
-        if (length > delta) { return true; }
-        return false;
 
+    private _attachScreenRayMode(xrController: WebXRInputSource) {
+        const controllerData = this._controllers[xrController.uniqueId];
+        let downTriggered = false;
+        controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
+            if (!controllerData.pick || (this._options.disablePointerUpOnTouchOut && downTriggered)) { return; }
+            if (!downTriggered) {
+                this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
+                downTriggered = true;
+                if (this._options.disablePointerUpOnTouchOut) {
+                    this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
+                }
+            } else {
+                this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
+            }
+        });
+        xrController.onDisposeObservable.addOnce(() => {
+            if (controllerData.pick && downTriggered && !this._options.disablePointerUpOnTouchOut) {
+                this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
+            }
+        });
     }
 
     private _attachTrackedPointerRayMode(xrController: WebXRInputSource) {
@@ -440,7 +416,16 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
                 }
             });
         });
+    }
 
+    private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
+        if (normal) {
+            let angle = Math.acos(Vector3.Dot(normal, ray.direction));
+            if (angle < Math.PI / 2) {
+                normal.scaleInPlace(-1);
+            }
+        }
+        return normal;
     }
 
     private _detachController(xrControllerUniqueId: string) {
@@ -504,14 +489,16 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
         };
     }
 
-    private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
-        if (normal) {
-            let angle = Math.acos(Vector3.Dot(normal, ray.direction));
-            if (angle < Math.PI / 2) {
-                normal.scaleInPlace(-1);
-            }
-        }
-        return normal;
+    private _pickingMoved(oldPick: PickingInfo, newPick: PickingInfo) {
+        if (!oldPick.hit || !newPick.hit) { return true; }
+        if (!oldPick.pickedMesh || !oldPick.pickedPoint || !newPick.pickedMesh || !newPick.pickedPoint) { return true; }
+        if (oldPick.pickedMesh !== newPick.pickedMesh) { return true; }
+        oldPick.pickedPoint?.subtractToRef(newPick.pickedPoint, this._tmpVectorForPickCompare);
+        this._tmpVectorForPickCompare.set(Math.abs(this._tmpVectorForPickCompare.x), Math.abs(this._tmpVectorForPickCompare.y), Math.abs(this._tmpVectorForPickCompare.z));
+        const delta = (this._options.gazeModePointerMovedFactor || 1) * 0.01 / newPick.distance;
+        const length = this._tmpVectorForPickCompare.length();
+        if (length > delta) { return true; }
+        return false;
     }
 
     private _updatePointerDistance(_laserPointer: AbstractMesh, distance: number = 100) {

+ 201 - 213
src/XR/features/WebXRControllerTeleportation.ts

@@ -30,38 +30,9 @@ import { UtilityLayerRenderer } from '../../Rendering/utilityLayerRenderer';
  */
 export interface IWebXRTeleportationOptions {
     /**
-     * Babylon XR Input class for controller
-     */
-    xrInput: WebXRInput;
-    /**
-     * A list of meshes to use as floor meshes.
-     * Meshes can be added and removed after initializing the feature using the
-     * addFloorMesh and removeFloorMesh functions
-     * If empty, rotation will still work
-     */
-    floorMeshes?: AbstractMesh[];
-    /**
-     * Provide your own teleportation mesh instead of babylon's wonderful doughnut.
-     * If you want to support rotation, make sure your mesh has a direction indicator.
-     *
-     * When left untouched, the default mesh will be initialized.
-     */
-    teleportationTargetMesh?: AbstractMesh;
-
-    /**
-     * An array of points to which the teleportation will snap to.
-     * If the teleportation ray is in the proximity of one of those points, it will be corrected to this point.
-     */
-    snapPositions?: Vector3[];
-    /**
-     * How close should the teleportation ray be in order to snap to position.
-     * Default to 0.8 units (meters)
-     */
-    snapToPositionRadius?: number;
-    /**
-     * Should teleportation move only to snap points
+     * if provided, this scene will be used to render meshes.
      */
-    snapPointsOnly?: boolean;
+    customUtilityLayerScene?: Scene;
     /**
      * Values to configure the default target mesh
      */
@@ -87,39 +58,88 @@ export interface IWebXRTeleportationOptions {
          */
         torusArrowMaterial?: Material;
     };
-
     /**
-     * Disable using the thumbstick and use the main component (usuallly trigger) on long press.
-     * This will be automatically true if the controller doesnt have a thumbstick or touchpad.
+     * A list of meshes to use as floor meshes.
+     * Meshes can be added and removed after initializing the feature using the
+     * addFloorMesh and removeFloorMesh functions
+     * If empty, rotation will still work
      */
-    useMainComponentOnly?: boolean;
-
+    floorMeshes?: AbstractMesh[];
+    /**
+     *  use this rendering group id for the meshes (optional)
+     */
+    renderingGroupId?: number;
+    /**
+     * Should teleportation move only to snap points
+     */
+    snapPointsOnly?: boolean;
+    /**
+     * An array of points to which the teleportation will snap to.
+     * If the teleportation ray is in the proximity of one of those points, it will be corrected to this point.
+     */
+    snapPositions?: Vector3[];
+    /**
+     * How close should the teleportation ray be in order to snap to position.
+     * Default to 0.8 units (meters)
+     */
+    snapToPositionRadius?: number;
+    /**
+     * Provide your own teleportation mesh instead of babylon's wonderful doughnut.
+     * If you want to support rotation, make sure your mesh has a direction indicator.
+     *
+     * When left untouched, the default mesh will be initialized.
+     */
+    teleportationTargetMesh?: AbstractMesh;
     /**
-     * If main component is used (no thumbstick), how long should the "long press" take before teleporting
+     * If main component is used (no thumbstick), how long should the "long press" take before teleport
      */
     timeToTeleport?: number;
     /**
-     * Should meshes created here be added to a utility layer or the main scene
+     * Disable using the thumbstick and use the main component (usually trigger) on long press.
+     * This will be automatically true if the controller doesn't have a thumbstick or touchpad.
      */
-    useUtilityLayer?: boolean;
-
+    useMainComponentOnly?: boolean;
     /**
-     * if provided, this scene will be used to render meshes.
+     * Should meshes created here be added to a utility layer or the main scene
      */
-    customUtilityLayerScene?: Scene;
-
+    useUtilityLayer?: boolean;
     /**
-     *  use this rendering group id for the meshes (optional)
+     * Babylon XR Input class for controller
      */
-    renderingGroupId?: number;
+    xrInput: WebXRInput;
 }
 
 /**
- * This is a teleportation feature to be used with webxr-enabled motion controllers.
+ * This is a teleportation feature to be used with WebXR-enabled motion controllers.
  * When enabled and attached, the feature will allow a user to move around and rotate in the scene using
  * the input of the attached controllers.
  */
 export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
+    private _controllers: {
+        [controllerUniqueId: string]: {
+            xrController: WebXRInputSource;
+            teleportationComponent?: WebXRControllerComponent;
+            teleportationState: {
+                forward: boolean;
+                backwards: boolean;
+                currentRotation: number;
+                baseRotation: number;
+                rotating: boolean;
+            }
+            onAxisChangedObserver?: Nullable<Observer<IWebXRMotionControllerAxesValue>>;
+            onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
+        };
+    } = {};
+    private _currentTeleportationControllerId: string;
+    private _floorMeshes: AbstractMesh[];
+    private _quadraticBezierCurve: AbstractMesh;
+    private _selectionFeature: IWebXRFeature;
+    private _snapToPositions: Vector3[];
+    private _snappedToPoint: boolean = false;
+    private _teleportationRingMaterial?: StandardMaterial;
+    private _tmpRay = new Ray(new Vector3(), new Vector3());
+    private _tmpVector = new Vector3();
+
     /**
      * The module's name
      */
@@ -132,16 +152,13 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
     public static readonly Version = 1;
 
     /**
-     * Is rotation enabled when moving forward?
-     * Disabling this feature will prevent the user from deciding the direction when teleporting
+     * Is movement backwards enabled
      */
-    public rotationEnabled: boolean = true;
+    public backwardsMovementEnabled = true;
     /**
-     * Should the module support parabolic ray on top of direct ray
-     * If enabled, the user will be able to point "at the sky" and move according to predefined radius distance
-     * Very helpful when moving between floors / different heights
+     * Distance to travel when moving backwards
      */
-    public parabolicRayEnabled: boolean = true;
+    public backwardsTeleportationDistance: number = 0.7;
     /**
      * The distance from the user to the inspection point in the direction of the controller
      * A higher number will allow the user to move further
@@ -149,71 +166,20 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
      */
     public parabolicCheckRadius: number = 5;
     /**
-     * How much rotation should be applied when rotating right and left
-     */
-    public rotationAngle: number = Math.PI / 8;
-
-    /**
-     * Is movement backwards enabled
-     */
-    public backwardsMovementEnabled = true;
-
-    /**
-     * Distance to travel when moving backwards
-     */
-    public backwardsTeleportationDistance: number = 0.7;
-
-    /**
-     * Add a new mesh to the floor meshes array
-     * @param mesh the mesh to use as floor mesh
+     * Should the module support parabolic ray on top of direct ray
+     * If enabled, the user will be able to point "at the sky" and move according to predefined radius distance
+     * Very helpful when moving between floors / different heights
      */
-    public addFloorMesh(mesh: AbstractMesh) {
-        this._floorMeshes.push(mesh);
-    }
-
+    public parabolicRayEnabled: boolean = true;
     /**
-     * Remove a mesh from the floor meshes array
-     * @param mesh the mesh to remove
+     * How much rotation should be applied when rotating right and left
      */
-    public removeFloorMesh(mesh: AbstractMesh) {
-        const index = this._floorMeshes.indexOf(mesh);
-        if (index !== -1) {
-            this._floorMeshes.splice(index, 1);
-        }
-    }
-
+    public rotationAngle: number = Math.PI / 8;
     /**
-     * Remove a mesh from the floor meshes array using its name
-     * @param name the mesh name to remove
+     * Is rotation enabled when moving forward?
+     * Disabling this feature will prevent the user from deciding the direction when teleporting
      */
-    public removeFloorMeshByName(name: string) {
-        const mesh = this._xrSessionManager.scene.getMeshByName(name);
-        if (mesh) {
-            this.removeFloorMesh(mesh);
-        }
-    }
-
-    private _tmpRay = new Ray(new Vector3(), new Vector3());
-    private _tmpVector = new Vector3();
-
-    private _floorMeshes: AbstractMesh[];
-    private _snapToPositions: Vector3[];
-
-    private _controllers: {
-        [controllerUniqueId: string]: {
-            xrController: WebXRInputSource;
-            teleportationComponent?: WebXRControllerComponent;
-            teleportationState: {
-                forward: boolean;
-                backwards: boolean;
-                currentRotation: number;
-                baseRotation: number;
-                rotating: boolean;
-            }
-            onAxisChangedObserver?: Nullable<Observer<IWebXRMotionControllerAxesValue>>;
-            onButtonChangedObserver?: Nullable<Observer<WebXRControllerComponent>>;
-        };
-    } = {};
+    public rotationEnabled: boolean = true;
 
     /**
      * constructs a new anchor system
@@ -233,10 +199,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._setTargetMeshVisibility(false);
     }
 
-    private _selectionFeature: IWebXRFeature;
-    private _snappedToPoint: boolean = false;
-    private _teleportationRingMaterial?: StandardMaterial;
-
     /**
      * Get the snapPointsOnly flag
      */
@@ -253,6 +215,14 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
     }
 
     /**
+     * Add a new mesh to the floor meshes array
+     * @param mesh the mesh to use as floor mesh
+     */
+    public addFloorMesh(mesh: AbstractMesh) {
+        this._floorMeshes.push(mesh);
+    }
+
+    /**
      * Add a new snap-to point to fix teleportation to this position
      * @param newSnapPoint The new Snap-To point
      */
@@ -260,6 +230,62 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._snapToPositions.push(newSnapPoint);
     }
 
+    public attach(): boolean {
+        if (!super.attach()) {
+            return false;
+        }
+
+        this._options.xrInput.controllers.forEach(this._attachController);
+        this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
+        this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
+            // REMOVE the controller
+            this._detachController(controller.uniqueId);
+        });
+
+        return true;
+    }
+
+    public detach(): boolean {
+        if (!super.detach()) {
+            return false;
+        }
+
+        Object.keys(this._controllers).forEach((controllerId) => {
+            this._detachController(controllerId);
+        });
+
+        this._setTargetMeshVisibility(false);
+
+        return true;
+    }
+
+    public dispose(): void {
+        super.dispose();
+        this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.dispose(false, true);
+    }
+
+    /**
+     * Remove a mesh from the floor meshes array
+     * @param mesh the mesh to remove
+     */
+    public removeFloorMesh(mesh: AbstractMesh) {
+        const index = this._floorMeshes.indexOf(mesh);
+        if (index !== -1) {
+            this._floorMeshes.splice(index, 1);
+        }
+    }
+
+    /**
+     * Remove a mesh from the floor meshes array using its name
+     * @param name the mesh name to remove
+     */
+    public removeFloorMeshByName(name: string) {
+        const mesh = this._xrSessionManager.scene.getMeshByName(name);
+        if (mesh) {
+            this.removeFloorMesh(mesh);
+        }
+    }
+
     /**
      * This function will iterate through the array, searching for this point or equal to it. It will then remove it from the snap-to array
      * @param snapPointToRemove the point (or a clone of it) to be removed from the array
@@ -285,6 +311,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         }
         return false;
     }
+
     /**
      * This function sets a selection feature that will be disabled when
      * the forward ray is shown and will be reattached when hidden.
@@ -295,40 +322,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._selectionFeature = selectionFeature;
     }
 
-    public attach(): boolean {
-        if (!super.attach()) {
-            return false;
-        }
-
-        this._options.xrInput.controllers.forEach(this._attachController);
-        this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
-        this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
-            // REMOVE the controller
-            this._detachController(controller.uniqueId);
-        });
-
-        return true;
-    }
-
-    public detach(): boolean {
-        if (!super.detach()) {
-            return false;
-        }
-
-        Object.keys(this._controllers).forEach((controllerId) => {
-            this._detachController(controllerId);
-        });
-
-        this._setTargetMeshVisibility(false);
-
-        return true;
-    }
-
-    public dispose(): void {
-        super.dispose();
-        this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.dispose(false, true);
-    }
-
     protected _onXRFrame(_xrFrame: XRFrame) {
         const frame = this._xrSessionManager.currentFrame;
         const scene = this._xrSessionManager.scene;
@@ -396,8 +389,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         }
     }
 
-    private _currentTeleportationControllerId: string;
-
     private _attachController = (xrController: WebXRInputSource) => {
         if (this._controllers[xrController.uniqueId]) {
             // already attached
@@ -490,7 +481,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
                                     // Teleport the users feet to where they targeted
                                     this._options.xrInput.xrCamera.position.addInPlace(pick.pickedPoint);
                                 }
-
                             }
                         }
                         if (axesData.y < -0.7 && !this._currentTeleportationControllerId && !controllerData.teleportationState.rotating) {
@@ -527,37 +517,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         });
     }
 
-    private _teleportForward(controllerId: string) {
-        const controllerData = this._controllers[controllerId];
-        controllerData.teleportationState.forward = false;
-        this._currentTeleportationControllerId = "";
-        if (this.snapPointsOnly && !this._snappedToPoint) {
-            return;
-        }
-        // do the movement forward here
-        if (this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.isVisible) {
-            const height = this._options.xrInput.xrCamera.realWorldHeight;
-            this._options.xrInput.xrCamera.position.copyFrom(this._options.teleportationTargetMesh.position);
-            this._options.xrInput.xrCamera.position.y += height;
-            this._options.xrInput.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, controllerData.teleportationState.currentRotation, 0));
-        }
-    }
-
-    private _detachController(xrControllerUniqueId: string) {
-        const controllerData = this._controllers[xrControllerUniqueId];
-        if (!controllerData) { return; }
-        if (controllerData.teleportationComponent) {
-            if (controllerData.onAxisChangedObserver) {
-                controllerData.teleportationComponent.onAxisValueChangedObservable.remove(controllerData.onAxisChangedObserver);
-            }
-            if (controllerData.onButtonChangedObserver) {
-                controllerData.teleportationComponent.onButtonStateChangedObservable.remove(controllerData.onButtonChangedObserver);
-            }
-        }
-        // remove from the map
-        delete this._controllers[xrControllerUniqueId];
-    }
-
     private _createDefaultTargetMesh() {
         // set defaults
         this._options.defaultTargetMeshOptions = this._options.defaultTargetMeshOptions || {};
@@ -649,6 +608,50 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._options.teleportationTargetMesh = teleportationTarget;
     }
 
+    private _detachController(xrControllerUniqueId: string) {
+        const controllerData = this._controllers[xrControllerUniqueId];
+        if (!controllerData) { return; }
+        if (controllerData.teleportationComponent) {
+            if (controllerData.onAxisChangedObserver) {
+                controllerData.teleportationComponent.onAxisValueChangedObservable.remove(controllerData.onAxisChangedObserver);
+            }
+            if (controllerData.onButtonChangedObserver) {
+                controllerData.teleportationComponent.onButtonStateChangedObservable.remove(controllerData.onButtonChangedObserver);
+            }
+        }
+        // remove from the map
+        delete this._controllers[xrControllerUniqueId];
+    }
+
+    private _findClosestSnapPointWithRadius(realPosition: Vector3, radius: number = this._options.snapToPositionRadius || 0.8) {
+        let closestPoint: Nullable<Vector3> = null;
+        let closestDistance = Number.MAX_VALUE;
+        if (this._snapToPositions.length) {
+            const radiusSquared = radius * radius;
+            this._snapToPositions.forEach((position) => {
+                const dist = Vector3.DistanceSquared(position, realPosition);
+                if (dist <= radiusSquared && dist < closestDistance) {
+                    closestDistance = dist;
+                    closestPoint = position;
+                }
+            });
+        }
+        return closestPoint;
+    }
+
+    private _setTargetMeshPosition(newPosition: Vector3) {
+        if (!this._options.teleportationTargetMesh) { return; }
+        const snapPosition = this._findClosestSnapPointWithRadius(newPosition);
+        this._snappedToPoint = !!snapPosition;
+        if (this.snapPointsOnly && !this._snappedToPoint && this._teleportationRingMaterial) {
+            this._teleportationRingMaterial.diffuseColor.set(1.0, 0.3, 0.3);
+        } else if (this.snapPointsOnly && this._snappedToPoint && this._teleportationRingMaterial) {
+            this._teleportationRingMaterial.diffuseColor.set(0.3, 0.3, 1.0);
+        }
+        this._options.teleportationTargetMesh.position.copyFrom(snapPosition || newPosition);
+        this._options.teleportationTargetMesh.position.y += 0.01;
+    }
+
     private _setTargetMeshVisibility(visible: boolean) {
         if (!this._options.teleportationTargetMesh) { return; }
         if (this._options.teleportationTargetMesh.isVisible === visible) { return; }
@@ -669,21 +672,6 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         }
     }
 
-    private _setTargetMeshPosition(newPosition: Vector3) {
-        if (!this._options.teleportationTargetMesh) { return; }
-        const snapPosition = this._findClosestSnapPointWithRadius(newPosition);
-        this._snappedToPoint = !!snapPosition;
-        if (this.snapPointsOnly && !this._snappedToPoint && this._teleportationRingMaterial) {
-            this._teleportationRingMaterial.diffuseColor.set(1.0, 0.3, 0.3);
-        } else if (this.snapPointsOnly && this._snappedToPoint && this._teleportationRingMaterial) {
-            this._teleportationRingMaterial.diffuseColor.set(0.3, 0.3, 1.0);
-        }
-        this._options.teleportationTargetMesh.position.copyFrom(snapPosition || newPosition);
-        this._options.teleportationTargetMesh.position.y += 0.01;
-    }
-
-    private _quadraticBezierCurve: AbstractMesh;
-
     private _showParabolicPath(pickInfo: PickingInfo) {
         if (!pickInfo.pickedPoint) { return; }
 
@@ -703,20 +691,20 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._quadraticBezierCurve.isPickable = false;
     }
 
-    private _findClosestSnapPointWithRadius(realPosition: Vector3, radius: number = this._options.snapToPositionRadius || 0.8) {
-        let closestPoint: Nullable<Vector3> = null;
-        let closestDistance = Number.MAX_VALUE;
-        if (this._snapToPositions.length) {
-            const radiusSquared = radius * radius;
-            this._snapToPositions.forEach((position) => {
-                const dist = Vector3.DistanceSquared(position, realPosition);
-                if (dist <= radiusSquared && dist < closestDistance) {
-                    closestDistance = dist;
-                    closestPoint = position;
-                }
-            });
+    private _teleportForward(controllerId: string) {
+        const controllerData = this._controllers[controllerId];
+        controllerData.teleportationState.forward = false;
+        this._currentTeleportationControllerId = "";
+        if (this.snapPointsOnly && !this._snappedToPoint) {
+            return;
+        }
+        // do the movement forward here
+        if (this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.isVisible) {
+            const height = this._options.xrInput.xrCamera.realWorldHeight;
+            this._options.xrInput.xrCamera.position.copyFrom(this._options.teleportationTargetMesh.position);
+            this._options.xrInput.xrCamera.position.y += height;
+            this._options.xrInput.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, controllerData.teleportationState.currentRotation, 0));
         }
-        return closestPoint;
     }
 }
 

+ 68 - 70
src/XR/features/WebXRHitTestLegacy.ts

@@ -26,21 +26,26 @@ export interface IWebXRHitTestOptions {
  */
 export interface IWebXRHitResult {
     /**
-     * The native hit test result
-     */
-    xrHitResult: XRHitResult;
-    /**
      * Transformation matrix that can be applied to a node that will put it in the hit point location
      */
     transformationMatrix: Matrix;
+    /**
+     * The native hit test result
+     */
+    xrHitResult: XRHitResult;
 }
 
 /**
  * The currently-working hit-test module.
- * Hit test (or raycasting) is used to interact with the real world.
+ * Hit test (or Ray-casting) is used to interact with the real world.
  * For further information read here - https://github.com/immersive-web/hit-test
  */
 export class WebXRHitTestLegacy extends WebXRAbstractFeature {
+    // in XR space z-forward is negative
+    private _direction = new Vector3(0, 0, -1);
+    private _mat = new Matrix();
+    private _onSelectEnabled = false;
+    private _origin = new Vector3(0, 0, 0);
 
     /**
      * The module's name
@@ -49,31 +54,37 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
+     * This number does not correspond to the WebXR specs version
      */
     public static readonly Version = 1;
 
     /**
-     * Execute a hit test on the current running session using a select event returned from a transient input (such as touch)
-     * @param event the (select) event to use to select with
-     * @param referenceSpace the reference space to use for this hit test
-     * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
+     * Populated with the last native XR Hit Results
      */
-    public static XRHitTestWithSelectEvent(event: XRInputSourceEvent, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]> {
-        let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, referenceSpace);
-        if (!targetRayPose) {
-            return Promise.resolve([]);
-        }
-        let targetRay = new XRRay(targetRayPose.transform);
+    public lastNativeXRHitResults: XRHitResult[] = [];
+    /**
+     * Triggered when new babylon (transformed) hit test results are available
+     */
+    public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
 
-        return this.XRHitTestWithRay(event.frame.session, targetRay, referenceSpace);
+    /**
+     * Creates a new instance of the (legacy version) hit test feature
+     * @param _xrSessionManager an instance of WebXRSessionManager
+     * @param options options to use when constructing this feature
+     */
+    constructor(_xrSessionManager: WebXRSessionManager,
+        /**
+         * options to use when constructing this feature
+         */
+        public readonly options: IWebXRHitTestOptions = {}) {
+        super(_xrSessionManager);
     }
 
     /**
      * execute a hit test with an XR Ray
      *
      * @param xrSession a native xrSession that will execute this hit test
-     * @param xrRay the ray (position and direction) to use for raycasting
+     * @param xrRay the ray (position and direction) to use for ray-casting
      * @param referenceSpace native XR reference space to use for the hit-test
      * @param filter filter function that will filter the results
      * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
@@ -86,36 +97,28 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
     }
 
     /**
-     * Triggered when new babylon (transformed) hit test results are available
+     * Execute a hit test on the current running session using a select event returned from a transient input (such as touch)
+     * @param event the (select) event to use to select with
+     * @param referenceSpace the reference space to use for this hit test
+     * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
      */
-    public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
+    public static XRHitTestWithSelectEvent(event: XRInputSourceEvent, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]> {
+        let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, referenceSpace);
+        if (!targetRayPose) {
+            return Promise.resolve([]);
+        }
+        let targetRay = new XRRay(targetRayPose.transform);
 
-    private _onSelectEnabled = false;
-    /**
-     * Creates a new instance of the (legacy version) hit test feature
-     * @param _xrSessionManager an instance of WebXRSessionManager
-     * @param options options to use when constructing this feature
-     */
-    constructor(_xrSessionManager: WebXRSessionManager,
-        /**
-         * options to use when constructing this feature
-         */
-        public readonly options: IWebXRHitTestOptions = {}) {
-        super(_xrSessionManager);
+        return this.XRHitTestWithRay(event.frame.session, targetRay, referenceSpace);
     }
 
     /**
-     * Populated with the last native XR Hit Results
-     */
-    public lastNativeXRHitResults: XRHitResult[] = [];
-
-    /**
      * attach this feature
      * Will usually be called by the features manager
      *
      * @returns true if successful.
      */
-    attach(): boolean {
+    public attach(): boolean {
         if (!super.attach()) {
             return false;
         }
@@ -132,7 +135,7 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
      *
      * @returns true if successful.
      */
-    detach(): boolean {
+    public detach(): boolean {
         if (!super.detach()) {
             return false;
         }
@@ -142,31 +145,14 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
         return true;
     }
 
-    private _onHitTestResults = (xrResults: XRHitResult[]) => {
-        const mats = xrResults.map((result) => {
-            let mat = Matrix.FromArray(result.hitMatrix);
-            if (!this._xrSessionManager.scene.useRightHandedSystem) {
-                mat.toggleModelMatrixHandInPlace();
-            }
-            // if (this.options.coordinatesSpace === Space.WORLD) {
-            if (this.options.worldParentNode) {
-                mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
-            }
-            return {
-                xrHitResult: result,
-                transformationMatrix: mat
-            };
-        });
-
-        this.lastNativeXRHitResults = xrResults;
-        this.onHitTestResultObservable.notifyObservers(mats);
+    /**
+     * Dispose this feature and all of the resources attached
+     */
+    public dispose(): void {
+        super.dispose();
+        this.onHitTestResultObservable.clear();
     }
 
-    private _origin = new Vector3(0, 0, 0);
-    // in XR space z-forward is negative
-    private _direction = new Vector3(0, 0, -1);
-    private _mat = new Matrix();
-
     protected _onXRFrame(frame: XRFrame) {
         // make sure we do nothing if (async) not attached
         if (!this.attached || this.options.testOnPointerDownOnly) {
@@ -186,6 +172,26 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
         WebXRHitTestLegacy.XRHitTestWithRay(this._xrSessionManager.session, ray, this._xrSessionManager.referenceSpace).then(this._onHitTestResults);
     }
 
+    private _onHitTestResults = (xrResults: XRHitResult[]) => {
+        const mats = xrResults.map((result) => {
+            let mat = Matrix.FromArray(result.hitMatrix);
+            if (!this._xrSessionManager.scene.useRightHandedSystem) {
+                mat.toggleModelMatrixHandInPlace();
+            }
+            // if (this.options.coordinatesSpace === Space.WORLD) {
+            if (this.options.worldParentNode) {
+                mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
+            }
+            return {
+                xrHitResult: result,
+                transformationMatrix: mat
+            };
+        });
+
+        this.lastNativeXRHitResults = xrResults;
+        this.onHitTestResultObservable.notifyObservers(mats);
+    }
+
     // can be done using pointerdown event, and xrSessionManager.currentFrame
     private _onSelect = (event: XRInputSourceEvent) => {
         if (!this._onSelectEnabled) {
@@ -193,14 +199,6 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature {
         }
         WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace);
     }
-
-    /**
-     * Dispose this feature and all of the resources attached
-     */
-    dispose(): void {
-        super.dispose();
-        this.onHitTestResultObservable.clear();
-    }
 }
 
 //register the plugin versions

+ 27 - 28
src/XR/features/WebXRPlaneDetector.ts

@@ -16,7 +16,7 @@ export interface IWebXRPlaneDetectorOptions {
 }
 
 /**
- * A babylon interface for a webxr plane.
+ * A babylon interface for a WebXR plane.
  * A Plane is actually a polygon, built from N points in space
  *
  * Supported in chrome 79, not supported in canary 81 ATM
@@ -27,10 +27,6 @@ export interface IWebXRPlane {
      */
     id: number;
     /**
-     * the native xr-plane object
-     */
-    xrPlane: XRPlane;
-    /**
      * an array of vector3 points in babylon space. right/left hand system is taken into account.
      */
     polygonDefinition: Array<Vector3>;
@@ -39,6 +35,10 @@ export interface IWebXRPlane {
      * Local vs. World are decided if worldParentNode was provided or not in the options when constructing the module
      */
     transformationMatrix: Matrix;
+    /**
+     * the native xr-plane object
+     */
+    xrPlane: XRPlane;
 }
 
 let planeIdProvider = 0;
@@ -48,6 +48,9 @@ let planeIdProvider = 0;
  * For more information see https://github.com/immersive-web/real-world-geometry/
  */
 export class WebXRPlaneDetector extends WebXRAbstractFeature {
+    private _detectedPlanes: Array<IWebXRPlane> = [];
+    private _enabled: boolean = false;
+    private _lastFrameDetected: XRPlaneSet = new Set();
 
     /**
      * The module's name
@@ -56,7 +59,7 @@ export class WebXRPlaneDetector extends WebXRAbstractFeature {
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
-     * This number does not correspond to the webxr specs version
+     * This number does not correspond to the WebXR specs version
      */
     public static readonly Version = 1;
 
@@ -74,10 +77,6 @@ export class WebXRPlaneDetector extends WebXRAbstractFeature {
      */
     public onPlaneUpdatedObservable: Observable<IWebXRPlane> = new Observable();
 
-    private _enabled: boolean = false;
-    private _detectedPlanes: Array<IWebXRPlane> = [];
-    private _lastFrameDetected: XRPlaneSet = new Set();
-
     /**
      * construct a new Plane Detector
      * @param _xrSessionManager an instance of xr Session manager
@@ -94,16 +93,14 @@ export class WebXRPlaneDetector extends WebXRAbstractFeature {
         }
     }
 
-    private _init() {
-        if (!this._xrSessionManager.session.updateWorldTrackingState) {
-            // fail silently
-            return;
-        }
-        this._xrSessionManager.session.updateWorldTrackingState({ planeDetectionState: { enabled: true } });
-        this._enabled = true;
-        if (this._detectedPlanes.length) {
-            this._detectedPlanes = [];
-        }
+    /**
+     * Dispose this feature and all of the resources attached
+     */
+    public dispose(): void {
+        super.dispose();
+        this.onPlaneAddedObservable.clear();
+        this.onPlaneRemovedObservable.clear();
+        this.onPlaneUpdatedObservable.clear();
     }
 
     protected _onXRFrame(frame: XRFrame) {
@@ -142,14 +139,16 @@ export class WebXRPlaneDetector extends WebXRAbstractFeature {
         }
     }
 
-    /**
-     * Dispose this feature and all of the resources attached
-     */
-    dispose(): void {
-        super.dispose();
-        this.onPlaneAddedObservable.clear();
-        this.onPlaneRemovedObservable.clear();
-        this.onPlaneUpdatedObservable.clear();
+    private _init() {
+        if (!this._xrSessionManager.session.updateWorldTrackingState) {
+            // fail silently
+            return;
+        }
+        this._xrSessionManager.session.updateWorldTrackingState({ planeDetectionState: { enabled: true } });
+        this._enabled = true;
+        if (this._detectedPlanes.length) {
+            this._detectedPlanes = [];
+        }
     }
 
     private _updatePlaneWithXRPlane(xrPlane: XRPlane, plane: Partial<IWebXRPlane>, xrFrame: XRFrame): IWebXRPlane {

+ 117 - 120
src/XR/motionController/webXRAbstractMotionController.ts

@@ -30,9 +30,9 @@ export type MotionControllerComponentStateType = "default" | "touched" | "presse
  */
 export interface IMotionControllerLayout {
     /**
-     * Defines the main button component id
+     * Path to load the assets. Usually relative to the base path
      */
-    selectComponentId: string;
+    assetPath: string;
     /**
      * Available components (unsorted)
      */
@@ -76,7 +76,7 @@ export interface IMotionControllerLayout {
                      */
                     componentProperty: "xAxis" | "yAxis" | "button" | "state";
                     /**
-                     * What states influence this visual reponse
+                     * What states influence this visual response
                      */
                     states: MotionControllerComponentStateType[];
                     /**
@@ -112,9 +112,9 @@ export interface IMotionControllerLayout {
      */
     rootNodeName: string;
     /**
-     * Path to load the assets. Usually relative to the base path
+     * Defines the main button component id
      */
-    assetPath: string;
+    selectComponentId: string;
 }
 
 /**
@@ -134,11 +134,6 @@ export interface IMotionControllerLayoutMap {
  */
 export interface IMotionControllerProfile {
     /**
-     * The id of this profile
-     * correlates to the profile(s) in the xrInput.profiles array
-     */
-    profileId: string;
-    /**
      * fallback profiles for this profileId
      */
     fallbackProfileIds: string[];
@@ -146,6 +141,11 @@ export interface IMotionControllerProfile {
      * The layout map, with handness as key
      */
     layouts: IMotionControllerLayoutMap;
+    /**
+     * The id of this profile
+     * correlates to the profile(s) in the xrInput.profiles array
+     */
+    profileId: string;
 }
 
 /**
@@ -154,10 +154,6 @@ export interface IMotionControllerProfile {
  */
 export interface IMotionControllerButtonMeshMap {
     /**
-     * The mesh that will be changed when value changes
-     */
-    valueMesh: AbstractMesh;
-    /**
      * the mesh that defines the pressed value mesh position.
      * This is used to find the max-position of this button
      */
@@ -167,6 +163,10 @@ export interface IMotionControllerButtonMeshMap {
      * This is used to find the min (or initial) position of this button
      */
     unpressedMesh: AbstractMesh;
+    /**
+     * The mesh that will be changed when value changes
+     */
+    valueMesh: AbstractMesh;
 }
 
 /**
@@ -176,17 +176,17 @@ export interface IMotionControllerButtonMeshMap {
  */
 export interface IMotionControllerMeshMap {
     /**
-     * The mesh that will be changed when axis value changes
+     * the mesh that defines the maximum value mesh position.
      */
-    valueMesh: AbstractMesh;
+    maxMesh?: AbstractMesh;
     /**
      * the mesh that defines the minimum value mesh position.
      */
     minMesh?: AbstractMesh;
     /**
-     * the mesh that defines the maximum value mesh position.
+     * The mesh that will be changed when axis value changes
      */
-    maxMesh?: AbstractMesh;
+    valueMesh: AbstractMesh;
 }
 
 /**
@@ -194,6 +194,10 @@ export interface IMotionControllerMeshMap {
  */
 export interface IMinimalMotionControllerObject {
     /**
+     * Available axes of this controller
+     */
+    axes: number[];
+    /**
      * An array of available buttons
      */
     buttons: Array<{
@@ -210,10 +214,6 @@ export interface IMinimalMotionControllerObject {
          */
         pressed: boolean;
     }>;
-    /**
-     * Available axes of this controller
-     */
-    axes: number[];
 }
 
 /**
@@ -222,11 +222,21 @@ export interface IMinimalMotionControllerObject {
  * Each component has an observable to check for changes in value and state
  */
 export abstract class WebXRAbstractMotionController implements IDisposable {
+    private _initComponent = (id: string) => {
+        if (!id) { return; }
+        const componentDef = this.layout.components[id];
+        const type = componentDef.type;
+        const buttonIndex = componentDef.gamepadIndices.button;
+        // search for axes
+        let axes: number[] = [];
+        if (componentDef.gamepadIndices.xAxis !== undefined && componentDef.gamepadIndices.yAxis !== undefined) {
+            axes.push(componentDef.gamepadIndices.xAxis, componentDef.gamepadIndices.yAxis);
+        }
 
-    /**
-     * The profile id of this motion controller
-     */
-    public abstract profileId: string;
+        this.components[id] = new WebXRControllerComponent(id, type, buttonIndex, axes);
+    }
+
+    private _modelReady: boolean = false;
 
     /**
      * A map of components (WebXRControllerComponent) in this motion controller
@@ -237,21 +247,21 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
     } = {};
 
     /**
+     * Disable the model's animation. Can be set at any time.
+     */
+    public disableAnimation: boolean = false;
+    /**
      * Observers registered here will be triggered when the model of this controller is done loading
      */
     public onModelLoadedObservable: Observable<WebXRAbstractMotionController> = new Observable();
-
     /**
-     * The root mesh of the model. It is null if the model was not yet initialized
+     * The profile id of this motion controller
      */
-    public rootMesh: Nullable<AbstractMesh>;
-
+    public abstract profileId: string;
     /**
-     * Disable the model's animation. Can be set at any time.
+     * The root mesh of the model. It is null if the model was not yet initialized
      */
-    public disableAnimation: boolean = false;
-
-    private _modelReady: boolean = false;
+    public rootMesh: Nullable<AbstractMesh>;
 
     /**
      * constructs a new abstract motion controller
@@ -278,43 +288,23 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
         // Model is loaded in WebXRInput
     }
 
-    private _initComponent = (id: string) => {
-        if (!id) { return; }
-        const componentDef = this.layout.components[id];
-        const type = componentDef.type;
-        const buttonIndex = componentDef.gamepadIndices.button;
-        // search for axes
-        let axes: number[] = [];
-        if (componentDef.gamepadIndices.xAxis !== undefined && componentDef.gamepadIndices.yAxis !== undefined) {
-            axes.push(componentDef.gamepadIndices.xAxis, componentDef.gamepadIndices.yAxis);
-        }
-
-        this.components[id] = new WebXRControllerComponent(id, type, buttonIndex, axes);
-    }
-
     /**
-     * Update this model using the current XRFrame
-     * @param xrFrame the current xr frame to use and update the model
-     */
-    public updateFromXRFrame(xrFrame: XRFrame): void {
-        this.getComponentIds().forEach((id) => this.getComponent(id).update(this.gamepadObject));
-        this.updateModel(xrFrame);
-    }
-
-    /**
-     * Get the list of components available in this motion controller
-     * @returns an array of strings correlating to available components
+     * Dispose this controller, the model mesh and all its components
      */
-    public getComponentIds(): string[] {
-        return Object.keys(this.components);
+    public dispose(): void {
+        this.getComponentIds().forEach((id) => this.getComponent(id).dispose());
+        if (this.rootMesh) {
+            this.rootMesh.dispose();
+        }
     }
 
     /**
-     * Get the main (Select) component of this controller as defined in the layout
-     * @returns the main component of this controller
+     * Returns all components of specific type
+     * @param type the type to search for
+     * @return an array of components with this type
      */
-    public getMainComponent(): WebXRControllerComponent {
-        return this.getComponent(this.layout.selectComponentId);
+    public getAllComponentsOfType(type: MotionControllerComponentType): WebXRControllerComponent[] {
+        return this.getComponentIds().map((id) => this.components[id]).filter((component) => component.type === type);
     }
 
     /**
@@ -327,6 +317,14 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
     }
 
     /**
+     * Get the list of components available in this motion controller
+     * @returns an array of strings correlating to available components
+     */
+    public getComponentIds(): string[] {
+        return Object.keys(this.components);
+    }
+
+    /**
      * Get the first component of specific type
      * @param type type of component to find
      * @return a controller component or null if not found
@@ -336,12 +334,11 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
     }
 
     /**
-     * Returns all components of specific type
-     * @param type the type to search for
-     * @return an array of components with this type
+     * Get the main (Select) component of this controller as defined in the layout
+     * @returns the main component of this controller
      */
-    public getAllComponentsOfType(type: MotionControllerComponentType): WebXRControllerComponent[] {
-        return this.getComponentIds().map((id) => this.components[id]).filter((component) => component.type === type);
+    public getMainComponent(): WebXRControllerComponent {
+        return this.getComponent(this.layout.selectComponentId);
     }
 
     /**
@@ -354,7 +351,7 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
         let loadingParams = this._getGenericFilenameAndPath();
         // Checking if GLB loader is present
         if (useGeneric) {
-            Logger.Warn("You need to reference GLTF loader to load Windows Motion Controllers model. Falling back to generic models");
+            Logger.Warn("Falling back to generic models");
         } else {
             loadingParams = this._getFilenameAndPath();
         }
@@ -378,14 +375,22 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
     }
 
     /**
-     * Update the model itself with the current frame data
-     * @param xrFrame the frame to use for updating the model mesh
+     * Update this model using the current XRFrame
+     * @param xrFrame the current xr frame to use and update the model
      */
-    protected updateModel(xrFrame: XRFrame): void {
-        if (!this._modelReady) {
-            return;
-        }
-        this._updateModel(xrFrame);
+    public updateFromXRFrame(xrFrame: XRFrame): void {
+        this.getComponentIds().forEach((id) => this.getComponent(id).update(this.gamepadObject));
+        this.updateModel(xrFrame);
+    }
+
+    // Look through all children recursively. This will return null if no mesh exists with the given name.
+    protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh {
+        return <AbstractMesh>node.getChildren((n) => n.name === name, false)[0];
+    }
+
+    // Look through only immediate children. This will return null if no mesh exists with the given name.
+    protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh {
+        return <AbstractMesh>node.getChildren((n) => n.name == name, true)[0];
     }
 
     /**
@@ -417,33 +422,15 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
             axisMap.valueMesh.position);
     }
 
-    // Look through all children recursively. This will return null if no mesh exists with the given name.
-    protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>node.getChildren((n) => n.name === name, false)[0];
-    }
-    // Look through only immediate children. This will return null if no mesh exists with the given name.
-    protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>node.getChildren((n) => n.name == name, true)[0];
-    }
-
-    private _getGenericFilenameAndPath(): { filename: string, path: string } {
-        return {
-            filename: "generic.babylon",
-            path: "https://controllers.babylonjs.com/generic/"
-        };
-    }
-
-    private _getGenericParentMesh(meshes: AbstractMesh[]): void {
-        this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
-
-        meshes.forEach((mesh) => {
-            if (!mesh.parent) {
-                mesh.isPickable = false;
-                mesh.setParent(this.rootMesh);
-            }
-        });
-
-        this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+    /**
+     * Update the model itself with the current frame data
+     * @param xrFrame the frame to use for updating the model mesh
+     */
+    protected updateModel(xrFrame: XRFrame): void {
+        if (!this._modelReady) {
+            return;
+        }
+        this._updateModel(xrFrame);
     }
 
     /**
@@ -452,6 +439,13 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
      */
     protected abstract _getFilenameAndPath(): { filename: string, path: string };
     /**
+     * This function is called before the mesh is loaded. It checks for loading constraints.
+     * For example, this function can check if the GLB loader is available
+     * If this function returns false, the generic controller will be loaded instead
+     * @returns Is the client ready to load the mesh
+     */
+    protected abstract _getModelLoadingConstraints(): boolean;
+    /**
      * This function will be called after the model was successfully loaded and can be used
      * for mesh transformations before it is available for the user
      * @param meshes the loaded meshes
@@ -467,21 +461,24 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
      * @param xrFrame the current xrFrame
      */
     protected abstract _updateModel(xrFrame: XRFrame): void;
-    /**
-     * This function is called before the mesh is loaded. It checks for loading constraints.
-     * For example, this function can check if the GLB loader is available
-     * If this function returns false, the generic controller will be loaded instead
-     * @returns Is the client ready to load the mesh
-     */
-    protected abstract _getModelLoadingConstraints(): boolean;
 
-    /**
-     * Dispose this controller, the model mesh and all its components
-     */
-    public dispose(): void {
-        this.getComponentIds().forEach((id) => this.getComponent(id).dispose());
-        if (this.rootMesh) {
-            this.rootMesh.dispose();
-        }
+    private _getGenericFilenameAndPath(): { filename: string, path: string } {
+        return {
+            filename: "generic.babylon",
+            path: "https://controllers.babylonjs.com/generic/"
+        };
+    }
+
+    private _getGenericParentMesh(meshes: AbstractMesh[]): void {
+        this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
+
+        meshes.forEach((mesh) => {
+            if (!mesh.parent) {
+                mesh.isPickable = false;
+                mesh.setParent(this.rootMesh);
+            }
+        });
+
+        this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
     }
 }

+ 58 - 58
src/XR/motionController/webXRControllerComponent.ts

@@ -35,28 +35,45 @@ export interface IWebXRMotionControllerComponentChangesValues<T> {
  */
 export interface IWebXRMotionControllerComponentChanges {
     /**
-     * will be populated with previous and current values if touched changed
+     * will be populated with previous and current values if axes changed
      */
-    touched?: IWebXRMotionControllerComponentChangesValues<boolean>;
+    axes?: IWebXRMotionControllerComponentChangesValues<IWebXRMotionControllerAxesValue>;
     /**
      * will be populated with previous and current values if pressed changed
      */
     pressed?: IWebXRMotionControllerComponentChangesValues<boolean>;
     /**
-     * will be populated with previous and current values if value changed
+     * will be populated with previous and current values if touched changed
      */
-    value?: IWebXRMotionControllerComponentChangesValues<number>;
+    touched?: IWebXRMotionControllerComponentChangesValues<boolean>;
     /**
-     * will be populated with previous and current values if axes changed
+     * will be populated with previous and current values if value changed
      */
-    axes?: IWebXRMotionControllerComponentChangesValues<IWebXRMotionControllerAxesValue>;
+    value?: IWebXRMotionControllerComponentChangesValues<number>;
 }
 /**
  * This class represents a single component (for example button or thumbstick) of a motion controller
  */
 export class WebXRControllerComponent implements IDisposable {
+    private _axes: IWebXRMotionControllerAxesValue = {
+        x: 0,
+        y: 0
+    };
+    private _changes: IWebXRMotionControllerComponentChanges = {};
+    private _currentValue: number = 0;
+    private _hasChanges: boolean = false;
+    private _pressed: boolean = false;
+    private _touched: boolean = false;
 
     /**
+     * button component type
+     */
+    public static BUTTON_TYPE: MotionControllerComponentType = "button";
+    /**
+     * squeeze component type
+     */
+    public static SQUEEZE_TYPE: MotionControllerComponentType = "squeeze";
+    /**
      * Thumbstick component type
      */
     public static THUMBSTICK_TYPE: MotionControllerComponentType = "thumbstick";
@@ -68,40 +85,17 @@ export class WebXRControllerComponent implements IDisposable {
      * trigger component type
      */
     public static TRIGGER_TYPE: MotionControllerComponentType = "trigger";
-    /**
-     * squeeze component type
-     */
-    public static SQUEEZE_TYPE: MotionControllerComponentType = "squeeze";
-    /**
-     * button component type
-     */
-    public static BUTTON_TYPE: MotionControllerComponentType = "button";
-    /**
-     * Observers registered here will be triggered when the state of a button changes
-     * State change is either pressed / touched / value
-     */
-    public onButtonStateChangedObservable: Observable<WebXRControllerComponent> = new Observable();
+
     /**
      * If axes are available for this component (like a touchpad or thumbstick) the observers will be notified when
      * the axes data changes
      */
     public onAxisValueChangedObservable: Observable<{ x: number, y: number }> = new Observable();
-
-    private _currentValue: number = 0;
-    private _touched: boolean = false;
-    private _pressed: boolean = false;
-    private _axes: IWebXRMotionControllerAxesValue = {
-        x: 0,
-        y: 0
-    };
-    private _changes: IWebXRMotionControllerComponentChanges = {};
-    private _hasChanges: boolean = false;
     /**
-     * Return whether or not the component changed the last frame
+     * Observers registered here will be triggered when the state of a button changes
+     * State change is either pressed / touched / value
      */
-    public get hasChanges(): boolean {
-        return this._hasChanges;
-    }
+    public onButtonStateChangedObservable: Observable<WebXRControllerComponent> = new Observable();
 
     /**
      * Creates a new component for a motion controller.
@@ -123,14 +117,27 @@ export class WebXRControllerComponent implements IDisposable {
         public type: MotionControllerComponentType,
         private _buttonIndex: number = -1,
         private _axesIndices: number[] = []) {
+    }
 
+    /**
+     * The current axes data. If this component has no axes it will still return an object { x: 0, y: 0 }
+     */
+    public get axes(): IWebXRMotionControllerAxesValue {
+        return this._axes;
     }
 
     /**
-     * Get the current value of this component
+     * Get the changes. Elements will be populated only if they changed with their previous and current value
      */
-    public get value(): number {
-        return this._currentValue;
+    public get changes(): IWebXRMotionControllerComponentChanges {
+        return this._changes;
+    }
+
+    /**
+     * Return whether or not the component changed the last frame
+     */
+    public get hasChanges(): boolean {
+        return this._hasChanges;
     }
 
     /**
@@ -148,25 +155,18 @@ export class WebXRControllerComponent implements IDisposable {
     }
 
     /**
-     * The current axes data. If this component has no axes it will still return an object { x: 0, y: 0 }
-     */
-    public get axes(): IWebXRMotionControllerAxesValue {
-        return this._axes;
-    }
-
-    /**
-     * Get the changes. Elements will be populated only if they changed with their previous and current value
+     * Get the current value of this component
      */
-    public get changes(): IWebXRMotionControllerComponentChanges {
-        return this._changes;
+    public get value(): number {
+        return this._currentValue;
     }
 
     /**
-     * Is this component a button (hence - pressable)
-     * @returns true if can be pressed
+     * Dispose this component
      */
-    public isButton(): boolean {
-        return this._buttonIndex !== -1;
+    public dispose(): void {
+        this.onAxisValueChangedObservable.clear();
+        this.onButtonStateChangedObservable.clear();
     }
 
     /**
@@ -178,6 +178,14 @@ export class WebXRControllerComponent implements IDisposable {
     }
 
     /**
+     * Is this component a button (hence - pressable)
+     * @returns true if can be pressed
+     */
+    public isButton(): boolean {
+        return this._buttonIndex !== -1;
+    }
+
+    /**
      * update this component using the gamepad object it is in. Called on every frame
      * @param nativeController the native gamepad controller object
      */
@@ -264,12 +272,4 @@ export class WebXRControllerComponent implements IDisposable {
             this.onAxisValueChangedObservable.notifyObservers(this._axes);
         }
     }
-
-    /**
-     * Dispose this component
-     */
-    public dispose(): void {
-        this.onAxisValueChangedObservable.clear();
-        this.onButtonStateChangedObservable.clear();
-    }
 }

+ 10 - 11
src/XR/motionController/webXRGenericMotionController.ts

@@ -24,14 +24,6 @@ export class WebXRGenericTriggerMotionController extends WebXRAbstractMotionCont
         super(scene, GenericTriggerLayout[handness], gamepadObject, handness);
     }
 
-    protected _processLoadedModel(meshes: AbstractMesh[]): void {
-        // nothing to do
-    }
-
-    protected _updateModel(): void {
-        // no-op
-    }
-
     protected _getFilenameAndPath(): { filename: string; path: string; } {
         return {
             filename: "generic.babylon",
@@ -39,6 +31,14 @@ export class WebXRGenericTriggerMotionController extends WebXRAbstractMotionCont
         };
     }
 
+    protected _getModelLoadingConstraints(): boolean {
+        return true;
+    }
+
+    protected _processLoadedModel(meshes: AbstractMesh[]): void {
+        // nothing to do
+    }
+
     protected _setRootMesh(meshes: AbstractMesh[]): void {
         this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
 
@@ -52,10 +52,9 @@ export class WebXRGenericTriggerMotionController extends WebXRAbstractMotionCont
         this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
     }
 
-    protected _getModelLoadingConstraints(): boolean {
-        return true;
+    protected _updateModel(): void {
+        // no-op
     }
-
 }
 
 // https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/generic/generic-trigger-touchpad-thumbstick.json

+ 19 - 33
src/XR/motionController/webXRHTCViveMotionController.ts

@@ -14,6 +14,8 @@ import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
  * The motion controller class for the standard HTC-Vive controllers
  */
 export class WebXRHTCViveMotionController extends WebXRAbstractMotionController {
+    private _modelRootNode: AbstractMesh;
+
     /**
      * The base url used to load the left and right controller models
      */
@@ -25,8 +27,6 @@ export class WebXRHTCViveMotionController extends WebXRAbstractMotionController
 
     public profileId = "htc-vive";
 
-    private _modelRootNode: AbstractMesh;
-
     /**
      * Create a new Vive motion controller object
      * @param scene the scene to use to create this controller
@@ -39,12 +39,25 @@ export class WebXRHTCViveMotionController extends WebXRAbstractMotionController
         super(scene, HTCViveLayout[handness], gamepadObject, handness);
     }
 
+    protected _getFilenameAndPath(): { filename: string; path: string; } {
+        let filename = WebXRHTCViveMotionController.MODEL_FILENAME;
+        let path = WebXRHTCViveMotionController.MODEL_BASE_URL;
+
+        return {
+            filename,
+            path
+        };
+    }
+
+    protected _getModelLoadingConstraints(): boolean {
+        return true;
+    }
+
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
         this.getComponentIds().forEach((id) => {
             const comp = id && this.getComponent(id);
             if (comp) {
                 comp.onButtonStateChangedObservable.add((component) => {
-
                     if (!this.rootMesh || this.disableAnimation) { return; }
 
                     switch (id) {
@@ -61,24 +74,6 @@ export class WebXRHTCViveMotionController extends WebXRAbstractMotionController
         });
     }
 
-    protected _getFilenameAndPath(): { filename: string; path: string; } {
-        let filename = WebXRHTCViveMotionController.MODEL_FILENAME;
-        let path = WebXRHTCViveMotionController.MODEL_BASE_URL;
-
-        return {
-            filename,
-            path
-        };
-    }
-
-    protected _updateModel(): void {
-        // no-op. model is updated using observables.
-    }
-
-    protected _getModelLoadingConstraints(): boolean {
-        return true;
-    }
-
     protected _setRootMesh(meshes: AbstractMesh[]): void {
         this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
 
@@ -88,6 +83,9 @@ export class WebXRHTCViveMotionController extends WebXRAbstractMotionController
         this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
     }
 
+    protected _updateModel(): void {
+        // no-op. model is updated using observables.
+    }
 }
 
 // register the profile
@@ -110,7 +108,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_trigger",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-squeeze": {
@@ -120,7 +117,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_squeeze",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-touchpad": {
@@ -132,7 +128,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_touchpad",
                 "visualResponses": {
-
                 },
             },
             "menu": {
@@ -142,7 +137,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "menu",
                 "visualResponses": {
-
                 }
             }
         },
@@ -160,7 +154,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_trigger",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-squeeze": {
@@ -170,7 +163,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_squeeze",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-touchpad": {
@@ -182,7 +174,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_touchpad",
                 "visualResponses": {
-
                 },
             },
             "menu": {
@@ -192,7 +183,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "menu",
                 "visualResponses": {
-
                 }
             }
         },
@@ -210,7 +200,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_trigger",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-squeeze": {
@@ -220,7 +209,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_squeeze",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-touchpad": {
@@ -232,7 +220,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_touchpad",
                 "visualResponses": {
-
                 },
             },
             "menu": {
@@ -242,7 +229,6 @@ const HTCViveLayout: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "menu",
                 "visualResponses": {
-
                 }
             }
         },

+ 43 - 40
src/XR/motionController/webXRMicrosoftMixedRealityController.ts

@@ -16,21 +16,6 @@ import { Logger } from '../../Misc/logger';
  * The motion controller class for all microsoft mixed reality controllers
  */
 export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
-    /**
-     * The base url used to load the left and right controller models
-     */
-    public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/microsoft/';
-    /**
-     * The name of the left controller model file
-     */
-    public static MODEL_LEFT_FILENAME: string = 'left.glb';
-    /**
-     * The name of the right controller model file
-     */
-    public static MODEL_RIGHT_FILENAME: string = 'right.glb';
-
-    public profileId = "microsoft-mixed-reality";
-
     // use this in the future - https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/assets/profiles/microsoft
     protected readonly _mapping = {
         defaultButton: {
@@ -85,10 +70,50 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
         }
     };
 
+    /**
+     * The base url used to load the left and right controller models
+     */
+    public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/microsoft/';
+    /**
+     * The name of the left controller model file
+     */
+    public static MODEL_LEFT_FILENAME: string = 'left.glb';
+    /**
+     * The name of the right controller model file
+     */
+    public static MODEL_RIGHT_FILENAME: string = 'right.glb';
+
+    public profileId = "microsoft-mixed-reality";
+
     constructor(scene: Scene, gamepadObject: IMinimalMotionControllerObject, handness: MotionControllerHandness) {
         super(scene, MixedRealityProfile["left-right"], gamepadObject, handness);
     }
 
+    protected _getFilenameAndPath(): { filename: string; path: string; } {
+        let filename = "";
+        if (this.handness === 'left') {
+            filename = WebXRMicrosoftMixedRealityController.MODEL_LEFT_FILENAME;
+        }
+        else { // Right is the default if no hand is specified
+            filename = WebXRMicrosoftMixedRealityController.MODEL_RIGHT_FILENAME;
+        }
+
+        const device = 'default';
+        let path = WebXRMicrosoftMixedRealityController.MODEL_BASE_URL + device + '/';
+        return {
+            filename,
+            path
+        };
+    }
+
+    protected _getModelLoadingConstraints(): boolean {
+        const glbLoaded = SceneLoader.IsPluginForExtensionAvailable(".glb");
+        if (!glbLoaded) {
+            Logger.Warn('glTF / glb loaded was not registered, using generic controller instead');
+        }
+        return glbLoaded;
+    }
+
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
         if (!this.rootMesh) { return; }
 
@@ -168,31 +193,6 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
         });
     }
 
-    protected _getFilenameAndPath(): { filename: string; path: string; } {
-        let filename = "";
-        if (this.handness === 'left') {
-            filename = WebXRMicrosoftMixedRealityController.MODEL_LEFT_FILENAME;
-        }
-        else { // Right is the default if no hand is specified
-            filename = WebXRMicrosoftMixedRealityController.MODEL_RIGHT_FILENAME;
-        }
-
-        const device = 'default';
-        let path = WebXRMicrosoftMixedRealityController.MODEL_BASE_URL + device + '/';
-        return {
-            filename,
-            path
-        };
-    }
-
-    protected _updateModel(): void {
-        // no-op. model is updated using observables.
-    }
-
-    protected _getModelLoadingConstraints(): boolean {
-        return SceneLoader.IsPluginForExtensionAvailable(".glb");
-    }
-
     protected _setRootMesh(meshes: AbstractMesh[]): void {
         this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
         this.rootMesh.isPickable = false;
@@ -216,6 +216,9 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
         this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
     }
 
+    protected _updateModel(): void {
+        // no-op. model is updated using observables.
+    }
 }
 
 // register the profile

+ 90 - 92
src/XR/motionController/webXRMotionControllerManager.ts

@@ -20,31 +20,62 @@ export type MotionControllerConstructor = (xrInput: XRInputSource, scene: Scene)
  * When using a model try to stay as generic as possible. Eventually there will be no need in any of the controller classes
  */
 export class WebXRMotionControllerManager {
+    private static _AvailableControllers: { [type: string]: MotionControllerConstructor } = {};
+    private static _Fallbacks: { [profileId: string]: string[] } = {};
+    // cache for loading
+    private static _ProfileLoadingPromises: { [profileName: string]: Promise<IMotionControllerProfile> } = {};
+    private static _ProfilesList: Promise<{ [profile: string]: string }>;
+
     /**
      * The base URL of the online controller repository. Can be changed at any time.
      */
     public static BaseRepositoryUrl = "https://immersive-web.github.io/webxr-input-profiles/packages/viewer/dist";
     /**
+     * Which repository gets priority - local or online
+     */
+    public static PrioritizeOnlineRepository: boolean = true;
+    /**
      * Use the online repository, or use only locally-defined controllers
      */
     public static UseOnlineRepository: boolean = true;
+
     /**
-     * Which repository gets priority - local or online
+     * Clear the cache used for profile loading and reload when requested again
      */
-    public static PrioritizeOnlineRepository: boolean = true;
-    private static _AvailableControllers: { [type: string]: MotionControllerConstructor } = {};
-    private static _Fallbacks: { [profileId: string]: string[] } = {};
+    public static ClearProfilesCache() {
+        delete this._ProfilesList;
+        this._ProfileLoadingPromises = {};
+    }
 
     /**
-     * Register a new controller based on its profile. This function will be called by the controller classes themselves.
-     *
-     * If you are missing a profile, make sure it is imported in your source, otherwise it will not register.
-     *
-     * @param type the profile type to register
-     * @param constructFunction the function to be called when loading this profile
+     * Register the default fallbacks.
+     * This function is called automatically when this file is imported.
      */
-    public static RegisterController(type: string, constructFunction: MotionControllerConstructor) {
-        this._AvailableControllers[type] = constructFunction;
+    public static DefaultFallbacks() {
+        this.RegisterFallbacksForProfileId("google-daydream", ["generic-touchpad"]);
+        this.RegisterFallbacksForProfileId("htc-vive-focus", ["generic-trigger-touchpad"]);
+        this.RegisterFallbacksForProfileId("htc-vive", ["generic-trigger-squeeze-touchpad"]);
+        this.RegisterFallbacksForProfileId("magicleap-one", ["generic-trigger-squeeze-touchpad"]);
+        this.RegisterFallbacksForProfileId("windows-mixed-reality", ["generic-trigger-squeeze-touchpad-thumbstick"]);
+        this.RegisterFallbacksForProfileId("microsoft-mixed-reality", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]);
+        this.RegisterFallbacksForProfileId("oculus-go", ["generic-trigger-touchpad"]);
+        this.RegisterFallbacksForProfileId("oculus-touch-v2", ["oculus-touch", "generic-trigger-squeeze-thumbstick"]);
+        this.RegisterFallbacksForProfileId("oculus-touch", ["generic-trigger-squeeze-thumbstick"]);
+        this.RegisterFallbacksForProfileId("samsung-gearvr", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]);
+        this.RegisterFallbacksForProfileId("samsung-odyssey", ["generic-touchpad"]);
+        this.RegisterFallbacksForProfileId("valve-index", ["generic-trigger-squeeze-touchpad-thumbstick"]);
+    }
+
+    /**
+     * Find a fallback profile if the profile was not found. There are a few predefined generic profiles.
+     * @param profileId the profile to which a fallback needs to be found
+     * @return an array with corresponding fallback profiles
+     */
+    public static FindFallbackWithProfileId(profileId: string): string[] {
+        const returnArray = this._Fallbacks[profileId] || [];
+
+        returnArray.unshift(profileId);
+        return returnArray;
     }
 
     /**
@@ -108,29 +139,41 @@ export class WebXRMotionControllerManager {
         }
     }
 
-    private static _LoadProfilesFromAvailableControllers(profileArray: string[], xrInput: XRInputSource, scene: Scene) {
-        // check fallbacks
-        for (let i = 0; i < profileArray.length; ++i) {
-            // defensive
-            if (!profileArray[i]) {
-                continue;
-            }
-            const fallbacks = this.FindFallbackWithProfileId(profileArray[i]);
-            for (let j = 0; j < fallbacks.length; ++j) {
-                const constructionFunction = this._AvailableControllers[fallbacks[j]];
-                if (constructionFunction) {
-                    return Promise.resolve(constructionFunction(xrInput, scene));
-                }
-            }
-        }
-
-        throw new Error(`no controller requested was found in the available controllers list`);
+    /**
+     * Register a new controller based on its profile. This function will be called by the controller classes themselves.
+     *
+     * If you are missing a profile, make sure it is imported in your source, otherwise it will not register.
+     *
+     * @param type the profile type to register
+     * @param constructFunction the function to be called when loading this profile
+     */
+    public static RegisterController(type: string, constructFunction: MotionControllerConstructor) {
+        this._AvailableControllers[type] = constructFunction;
     }
 
-    private static _ProfilesList: Promise<{ [profile: string]: string }>;
+    /**
+     * Register a fallback to a specific profile.
+     * @param profileId the profileId that will receive the fallbacks
+     * @param fallbacks A list of fallback profiles
+     */
+    public static RegisterFallbacksForProfileId(profileId: string, fallbacks: string[]): void {
+        if (this._Fallbacks[profileId]) {
+            this._Fallbacks[profileId].push(...fallbacks);
+        } else {
+            this._Fallbacks[profileId] = fallbacks;
+        }
+    }
 
-    // cache for loading
-    private static _ProfileLoadingPromises: { [profileName: string]: Promise<IMotionControllerProfile> } = {};
+    /**
+     * Will update the list of profiles available in the repository
+     * @return a promise that resolves to a map of profiles available online
+     */
+    public static UpdateProfilesList() {
+        this._ProfilesList = Tools.LoadFileAsync(this.BaseRepositoryUrl + '/profiles/profilesList.json', false).then((data) => {
+            return JSON.parse(data.toString());
+        });
+        return this._ProfilesList;
+    }
 
     private static _LoadProfileFromRepository(profileArray: string[], xrInput: XRInputSource, scene: Scene): Promise<WebXRAbstractMotionController> {
         return Promise.resolve().then(() => {
@@ -161,70 +204,25 @@ export class WebXRMotionControllerManager {
         }).then((profile: IMotionControllerProfile) => {
             return new WebXRProfiledMotionController(scene, xrInput, profile, this.BaseRepositoryUrl);
         });
-
-    }
-
-    /**
-     * Clear the cache used for profile loading and reload when requested again
-     */
-    public static ClearProfilesCache() {
-        delete this._ProfilesList;
-        this._ProfileLoadingPromises = {};
-    }
-
-    /**
-     * Will update the list of profiles available in the repository
-     * @return a promise that resolves to a map of profiles available online
-     */
-    public static UpdateProfilesList() {
-        this._ProfilesList = Tools.LoadFileAsync(this.BaseRepositoryUrl + '/profiles/profilesList.json', false).then((data) => {
-            return JSON.parse(data.toString());
-        });
-        return this._ProfilesList;
     }
 
-    /**
-     * Find a fallback profile if the profile was not found. There are a few predefined generic profiles.
-     * @param profileId the profile to which a fallback needs to be found
-     * @return an array with corresponding fallback profiles
-     */
-    public static FindFallbackWithProfileId(profileId: string): string[] {
-        const returnArray = this._Fallbacks[profileId] || [];
-
-        returnArray.unshift(profileId);
-        return returnArray;
-    }
-
-    /**
-     * Register a fallback to a specific profile.
-     * @param profileId the profileId that will receive the fallbacks
-     * @param fallbacks A list of fallback profiles
-     */
-    public static RegisterFallbacksForProfileId(profileId: string, fallbacks: string[]): void {
-        if (this._Fallbacks[profileId]) {
-            this._Fallbacks[profileId].push(...fallbacks);
-        } else {
-            this._Fallbacks[profileId] = fallbacks;
+    private static _LoadProfilesFromAvailableControllers(profileArray: string[], xrInput: XRInputSource, scene: Scene) {
+        // check fallbacks
+        for (let i = 0; i < profileArray.length; ++i) {
+            // defensive
+            if (!profileArray[i]) {
+                continue;
+            }
+            const fallbacks = this.FindFallbackWithProfileId(profileArray[i]);
+            for (let j = 0; j < fallbacks.length; ++j) {
+                const constructionFunction = this._AvailableControllers[fallbacks[j]];
+                if (constructionFunction) {
+                    return Promise.resolve(constructionFunction(xrInput, scene));
+                }
+            }
         }
-    }
 
-    /**
-     * Register the default fallbacks.
-     * This function is called automatically when this file is imported.
-     */
-    public static DefaultFallbacks() {
-        this.RegisterFallbacksForProfileId("google-daydream", ["generic-touchpad"]);
-        this.RegisterFallbacksForProfileId("htc-vive-focus", ["generic-trigger-touchpad"]);
-        this.RegisterFallbacksForProfileId("htc-vive", ["generic-trigger-squeeze-touchpad"]);
-        this.RegisterFallbacksForProfileId("magicleap-one", ["generic-trigger-squeeze-touchpad"]);
-        this.RegisterFallbacksForProfileId("windows-mixed-reality", ["generic-trigger-squeeze-touchpad-thumbstick"]);
-        this.RegisterFallbacksForProfileId("microsoft-mixed-reality", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]);
-        this.RegisterFallbacksForProfileId("oculus-go", ["generic-trigger-touchpad"]);
-        this.RegisterFallbacksForProfileId("oculus-touch-v2", ["oculus-touch", "generic-trigger-squeeze-thumbstick"]);
-        this.RegisterFallbacksForProfileId("oculus-touch", ["generic-trigger-squeeze-thumbstick"]);
-        this.RegisterFallbacksForProfileId("samsung-gearvr", ["windows-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]);
-        this.RegisterFallbacksForProfileId("samsung-odyssey", ["generic-touchpad"]);
-        this.RegisterFallbacksForProfileId("valve-index", ["generic-trigger-squeeze-touchpad-thumbstick"]);
+        throw new Error(`no controller requested was found in the available controllers list`);
     }
 }
 

+ 35 - 194
src/XR/motionController/webXROculusTouchMotionController.ts

@@ -15,6 +15,8 @@ import { Quaternion } from '../../Maths/math.vector';
  * This class supports legacy mapping as well the standard xr mapping
  */
 export class WebXROculusTouchMotionController extends WebXRAbstractMotionController {
+    private _modelRootNode: AbstractMesh;
+
     /**
      * The base url used to load the left and right controller models
      */
@@ -27,7 +29,6 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
      * The name of the right controller model file
      */
     public static MODEL_RIGHT_FILENAME: string = 'right.babylon';
-
     /**
      * Base Url for the Quest controller model.
      */
@@ -35,18 +36,35 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
 
     public profileId = "oculus-touch";
 
-    private _modelRootNode: AbstractMesh;
-
     constructor(scene: Scene,
         gamepadObject: IMinimalMotionControllerObject,
         handness: MotionControllerHandness,
         legacyMapping: boolean = false,
         private _forceLegacyControllers: boolean = false) {
-        super(scene, legacyMapping ? OculusTouchLegacyLayouts[handness] : OculusTouchLayouts[handness], gamepadObject, handness);
+        super(scene, OculusTouchLayouts[handness], gamepadObject, handness);
     }
 
-    protected _processLoadedModel(_meshes: AbstractMesh[]): void {
+    protected _getFilenameAndPath(): { filename: string; path: string; } {
+        let filename = "";
+        if (this.handness === 'left') {
+            filename = WebXROculusTouchMotionController.MODEL_LEFT_FILENAME;
+        }
+        else { // Right is the default if no hand is specified
+            filename = WebXROculusTouchMotionController.MODEL_RIGHT_FILENAME;
+        }
 
+        let path = this._isQuest() ? WebXROculusTouchMotionController.QUEST_MODEL_BASE_URL : WebXROculusTouchMotionController.MODEL_BASE_URL;
+        return {
+            filename,
+            path
+        };
+    }
+
+    protected _getModelLoadingConstraints(): boolean {
+        return true;
+    }
+
+    protected _processLoadedModel(_meshes: AbstractMesh[]): void {
         const isQuest = this._isQuest();
         const triggerDirection = this.handness === 'right' ? -1 : 1;
 
@@ -54,7 +72,6 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
             const comp = id && this.getComponent(id);
             if (comp) {
                 comp.onButtonStateChangedObservable.add((component) => {
-
                     if (!this.rootMesh || this.disableAnimation) { return; }
 
                     switch (id) {
@@ -100,39 +117,6 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
         });
     }
 
-    protected _getFilenameAndPath(): { filename: string; path: string; } {
-        let filename = "";
-        if (this.handness === 'left') {
-            filename = WebXROculusTouchMotionController.MODEL_LEFT_FILENAME;
-        }
-        else { // Right is the default if no hand is specified
-            filename = WebXROculusTouchMotionController.MODEL_RIGHT_FILENAME;
-        }
-
-        let path = this._isQuest() ? WebXROculusTouchMotionController.QUEST_MODEL_BASE_URL : WebXROculusTouchMotionController.MODEL_BASE_URL;
-        return {
-            filename,
-            path
-        };
-    }
-
-    /**
-     * Is this the new type of oculus touch. At the moment both have the same profile and it is impossible to differentiate
-     * between the touch and touch 2.
-     */
-    private _isQuest() {
-        // this is SADLY the only way to currently check. Until proper profiles will be available.
-        return !!navigator.userAgent.match(/Quest/gi) && !this._forceLegacyControllers;
-    }
-
-    protected _updateModel(): void {
-        // no-op. model is updated using observables.
-    }
-
-    protected _getModelLoadingConstraints(): boolean {
-        return true;
-    }
-
     protected _setRootMesh(meshes: AbstractMesh[]): void {
         this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
         this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
@@ -148,6 +132,18 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
         this._modelRootNode.parent = this.rootMesh;
     }
 
+    protected _updateModel(): void {
+        // no-op. model is updated using observables.
+    }
+
+    /**
+     * Is this the new type of oculus touch. At the moment both have the same profile and it is impossible to differentiate
+     * between the touch and touch 2.
+     */
+    private _isQuest() {
+        // this is SADLY the only way to currently check. Until proper profiles will be available.
+        return !!navigator.userAgent.match(/Quest/gi) && !this._forceLegacyControllers;
+    }
 }
 
 // register the profile
@@ -170,7 +166,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_trigger",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-squeeze": {
@@ -180,7 +175,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_squeeze",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-thumbstick": {
@@ -192,7 +186,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_thumbstick",
                 "visualResponses": {
-
                 }
             },
             "x-button": {
@@ -202,7 +195,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "x_button",
                 "visualResponses": {
-
                 }
             },
             "y-button": {
@@ -212,7 +204,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "y_button",
                 "visualResponses": {
-
                 }
             },
             "thumbrest": {
@@ -222,7 +213,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "thumbrest",
                 "visualResponses": {
-
                 }
             }
         },
@@ -240,7 +230,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_trigger",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-squeeze": {
@@ -250,7 +239,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_squeeze",
                 "visualResponses": {
-
                 }
             },
             "xr-standard-thumbstick": {
@@ -262,7 +250,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "xr_standard_thumbstick",
                 "visualResponses": {
-
                 }
             },
             "a-button": {
@@ -272,7 +259,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "a_button",
                 "visualResponses": {
-
                 }
             },
             "b-button": {
@@ -282,7 +268,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "b_button",
                 "visualResponses": {
-
                 }
             },
             "thumbrest": {
@@ -292,150 +277,6 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
                 },
                 "rootNodeName": "thumbrest",
                 "visualResponses": {
-
-                }
-            }
-        },
-        "gamepadMapping": "xr-standard",
-        "rootNodeName": "oculus-touch-v2-right",
-        "assetPath": "right.glb"
-    }
-};
-
-const OculusTouchLegacyLayouts: IMotionControllerLayoutMap = {
-    "left": {
-        "selectComponentId": "xr-standard-trigger",
-        "components": {
-            "xr-standard-trigger": {
-                "type": "trigger",
-                "gamepadIndices": {
-                    "button": 1
-                },
-                "rootNodeName": "xr_standard_trigger",
-                "visualResponses": {
-
-                }
-            },
-            "xr-standard-squeeze": {
-                "type": "squeeze",
-                "gamepadIndices": {
-                    "button": 2
-                },
-                "rootNodeName": "xr_standard_squeeze",
-                "visualResponses": {
-
-                }
-            },
-            "xr-standard-thumbstick": {
-                "type": "thumbstick",
-                "gamepadIndices": {
-                    "button": 0,
-                    "xAxis": 0,
-                    "yAxis": 1
-                },
-                "rootNodeName": "xr_standard_thumbstick",
-                "visualResponses": {
-
-                }
-            },
-            "x-button": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 3
-                },
-                "rootNodeName": "x_button",
-                "visualResponses": {
-
-                }
-            },
-            "y-button": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 4
-                },
-                "rootNodeName": "y_button",
-                "visualResponses": {
-
-                }
-            },
-            "thumbrest": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 5
-                },
-                "rootNodeName": "thumbrest",
-                "visualResponses": {
-
-                }
-            }
-        },
-        "gamepadMapping": "xr-standard",
-        "rootNodeName": "oculus-touch-v2-left",
-        "assetPath": "left.glb"
-    },
-    "right": {
-        "selectComponentId": "xr-standard-trigger",
-        "components": {
-            "xr-standard-trigger": {
-                "type": "trigger",
-                "gamepadIndices": {
-                    "button": 1
-                },
-                "rootNodeName": "xr_standard_trigger",
-                "visualResponses": {
-
-                }
-            },
-            "xr-standard-squeeze": {
-                "type": "squeeze",
-                "gamepadIndices": {
-                    "button": 2
-                },
-                "rootNodeName": "xr_standard_squeeze",
-                "visualResponses": {
-
-                }
-            },
-            "xr-standard-thumbstick": {
-                "type": "thumbstick",
-                "gamepadIndices": {
-                    "button": 0,
-                    "xAxis": 0,
-                    "yAxis": 1
-                },
-                "rootNodeName": "xr_standard_thumbstick",
-                "visualResponses": {
-
-                }
-            },
-            "a-button": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 3
-                },
-                "rootNodeName": "a_button",
-                "visualResponses": {
-
-                }
-            },
-            "b-button": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 4
-                },
-                "rootNodeName": "b_button",
-                "visualResponses": {
-
-                }
-            },
-            "thumbrest": {
-                "type": "button",
-                "gamepadIndices": {
-                    "button": 5
-                },
-                "rootNodeName": "thumbrest",
-                "visualResponses": {
-
                 }
             }
         },

+ 24 - 17
src/XR/motionController/webXRProfiledMotionController.ts

@@ -8,17 +8,13 @@ import { Color3 } from '../../Maths/math.color';
 import { WebXRControllerComponent } from './webXRControllerComponent';
 import { SphereBuilder } from '../../Meshes/Builders/sphereBuilder';
 import { StandardMaterial } from '../../Materials/standardMaterial';
+import { Logger } from '../../Misc/logger';
 
 /**
  * A profiled motion controller has its profile loaded from an online repository.
  * The class is responsible of loading the model, mapping the keys and enabling model-animations
  */
 export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
-    /**
-     * The profile ID of this controller. Will be populated when the controller initializes.
-     */
-    public profileId: string;
-
     private _buttonMeshMapping: {
         [buttonName: string]: {
             mainMesh: AbstractMesh;
@@ -27,18 +23,39 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
             }
         }
     } = {};
+    private _touchDots: { [visKey: string]: AbstractMesh } = {};
+
+    /**
+     * The profile ID of this controller. Will be populated when the controller initializes.
+     */
+    public profileId: string;
+
     constructor(scene: Scene, xrInput: XRInputSource, _profile: IMotionControllerProfile, private _repositoryUrl: string) {
         super(scene, _profile.layouts[xrInput.handedness || "none"], xrInput.gamepad as any, xrInput.handedness);
         this.profileId = _profile.profileId;
     }
 
+    public dispose() {
+        super.dispose();
+        Object.keys(this._touchDots).forEach((visResKey) => {
+            this._touchDots[visResKey].dispose();
+        });
+    }
+
     protected _getFilenameAndPath(): { filename: string; path: string; } {
         return {
             filename: this.layout.assetPath,
             path: `${this._repositoryUrl}/profiles/${this.profileId}/`
         };
     }
-    private _touchDots: { [visKey: string]: AbstractMesh } = {};
+
+    protected _getModelLoadingConstraints(): boolean {
+        const glbLoaded = SceneLoader.IsPluginForExtensionAvailable(".glb");
+        if (!glbLoaded) {
+            Logger.Warn('glTF / glb loaded was not registered, using generic controller instead');
+        }
+        return glbLoaded;
+    }
 
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
         this.getComponentIds().forEach((type) => {
@@ -100,6 +117,7 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
 
         this.rootMesh.rotate(Axis.Y, Math.PI, Space.WORLD);
     }
+
     protected _updateModel(_xrFrame: XRFrame): void {
         if (this.disableAnimation) {
             return;
@@ -129,15 +147,4 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
             });
         });
     }
-    protected _getModelLoadingConstraints(): boolean {
-        return SceneLoader.IsPluginForExtensionAvailable(".glb");
-    }
-
-    public dispose() {
-        super.dispose();
-        Object.keys(this._touchDots).forEach((visResKey) => {
-            this._touchDots[visResKey].dispose();
-        });
-    }
-
 }

+ 25 - 33
src/XR/webXRDefaultExperience.ts

@@ -14,42 +14,35 @@ import { Logger } from '../Misc/logger';
  */
 export class WebXRDefaultExperienceOptions {
     /**
-     * Floor meshes that will be used for teleporting
-     */
-    public floorMeshes?: Array<AbstractMesh>;
-
-    /**
      * Enable or disable default UI to enter XR
      */
     public disableDefaultUI?: boolean;
-
     /**
-     * optional configuration for the output canvas
+     * Should teleportation not initialize. defaults to false.
      */
-    public outputCanvasOptions?: WebXRManagedOutputCanvasOptions;
-
+    public disableTeleportation?: boolean;
     /**
-     * optional UI options. This can be used among other to change session mode and reference space type
+     * Floor meshes that will be used for teleport
      */
-    public uiOptions?: WebXREnterExitUIOptions;
-
+    public floorMeshes?: Array<AbstractMesh>;
+    /**
+     * If set to true, the first frame will not be used to reset position
+     * The first frame is mainly used when copying transformation from the old camera
+     * Mainly used in AR
+     */
+    public ignoreNativeCameraTransformation?: boolean;
     /**
      * Disable the controller mesh-loading. Can be used if you want to load your own meshes
      */
     public inputOptions?: IWebXRInputOptions;
-
     /**
-     * Should teleportation not initialize. defaults to false.
+     * optional configuration for the output canvas
      */
-    public disableTeleportation?: boolean;
-
+    public outputCanvasOptions?: WebXRManagedOutputCanvasOptions;
     /**
-     * If set to true, the first frame will not be used to reset position
-     * The first frame is mainly used when copying transformation from the old camera
-     * Mainly used in AR
+     * optional UI options. This can be used among other to change session mode and reference space type
      */
-    public ignoreNativeCameraTransformation?: boolean;
-
+    public uiOptions?: WebXREnterExitUIOptions;
     /**
      * When loading teleportation and pointer select, use stable versions instead of latest.
      */
@@ -65,6 +58,10 @@ export class WebXRDefaultExperience {
      */
     public baseExperience: WebXRExperienceHelper;
     /**
+     * Enables ui for entering/exiting xr
+     */
+    public enterExitUI: WebXREnterExitUI;
+    /**
      * Input experience extension
      */
     public input: WebXRInput;
@@ -73,17 +70,16 @@ export class WebXRDefaultExperience {
      */
     public pointerSelection: WebXRControllerPointerSelection;
     /**
-     * Enables teleportation
-     */
-    public teleportation: WebXRMotionControllerTeleportation;
-    /**
-     * Enables ui for entering/exiting xr
-     */
-    public enterExitUI: WebXREnterExitUI;
-    /**
      * Default target xr should render to
      */
     public renderTarget: WebXRRenderTarget;
+    /**
+     * Enables teleportation
+     */
+    public teleportation: WebXRMotionControllerTeleportation;
+
+    private constructor() {
+    }
 
     /**
      * Creates the default xr experience
@@ -140,10 +136,6 @@ export class WebXRDefaultExperience {
         });
     }
 
-    private constructor() {
-
-    }
-
     /**
      * DIsposes of the experience helper
      */

File diff suppressed because it is too large
+ 67 - 67
src/XR/webXREnterExitUI.ts


+ 50 - 56
src/XR/webXRExperienceHelper.ts

@@ -9,33 +9,20 @@ import { WebXRFeaturesManager } from './webXRFeaturesManager';
 import { Logger } from '../Misc/logger';
 
 /**
- * Base set of functionality needed to create an XR experince (WebXRSessionManager, Camera, StateManagement, etc.)
+ * Base set of functionality needed to create an XR experience (WebXRSessionManager, Camera, StateManagement, etc.)
  * @see https://doc.babylonjs.com/how_to/webxr
  */
 export class WebXRExperienceHelper implements IDisposable {
+    private _nonVRCamera: Nullable<Camera> = null;
+    private _originalSceneAutoClear = true;
+    private _supported = false;
+
     /**
      * Camera used to render xr content
      */
     public camera: WebXRCamera;
-
-    /**
-     * The current state of the XR experience (eg. transitioning, in XR or not in XR)
-     */
-    public state: WebXRState = WebXRState.NOT_IN_XR;
-
-    private _setState(val: WebXRState) {
-        if (this.state === val) {
-            return;
-        }
-        this.state = val;
-        this.onStateChangedObservable.notifyObservers(this.state);
-    }
-
-    /**
-     * Fires when the state of the experience helper has changed
-     */
-    public onStateChangedObservable = new Observable<WebXRState>();
-
+    /** A features manager for this xr session */
+    public featuresManager: WebXRFeaturesManager;
     /**
      * Observers registered here will be triggered after the camera's initial transformation is set
      * This can be used to set a different ground level or an extra rotation.
@@ -44,17 +31,30 @@ export class WebXRExperienceHelper implements IDisposable {
      * to the position set after this observable is done executing.
      */
     public onInitialXRPoseSetObservable = new Observable<WebXRCamera>();
-
+    /**
+     * Fires when the state of the experience helper has changed
+     */
+    public onStateChangedObservable = new Observable<WebXRState>();
     /** Session manager used to keep track of xr session */
     public sessionManager: WebXRSessionManager;
+    /**
+     * The current state of the XR experience (eg. transitioning, in XR or not in XR)
+     */
+    public state: WebXRState = WebXRState.NOT_IN_XR;
 
-    /** A features manager for this xr session */
-    public featuresManager: WebXRFeaturesManager;
-
-    private _nonVRCamera: Nullable<Camera> = null;
-    private _originalSceneAutoClear = true;
+    /**
+     * Creates a WebXRExperienceHelper
+     * @param scene The scene the helper should be created in
+     */
+    private constructor(private scene: Scene) {
+        this.sessionManager = new WebXRSessionManager(scene);
+        this.camera = new WebXRCamera("", scene, this.sessionManager);
+        this.featuresManager = new WebXRFeaturesManager(this.sessionManager);
 
-    private _supported = false;
+        scene.onDisposeObservable.add(() => {
+            this.exitXRAsync();
+        });
+    }
 
     /**
      * Creates the experience helper
@@ -74,26 +74,16 @@ export class WebXRExperienceHelper implements IDisposable {
     }
 
     /**
-     * Creates a WebXRExperienceHelper
-     * @param scene The scene the helper should be created in
-     */
-    private constructor(private scene: Scene) {
-        this.sessionManager = new WebXRSessionManager(scene);
-        this.camera = new WebXRCamera("", scene, this.sessionManager);
-        this.featuresManager = new WebXRFeaturesManager(this.sessionManager);
-
-        scene.onDisposeObservable.add(() => {
-            this.exitXRAsync();
-        });
-    }
-
-    /**
-     * Exits XR mode and returns the scene to its original state
-     * @returns promise that resolves after xr mode has exited
+     * Disposes of the experience helper
      */
-    public exitXRAsync() {
-        this._setState(WebXRState.EXITING_XR);
-        return this.sessionManager.exitXRAsync();
+    public dispose() {
+        this.camera.dispose();
+        this.onStateChangedObservable.clear();
+        this.onInitialXRPoseSetObservable.clear();
+        this.sessionManager.dispose();
+        if (this._nonVRCamera) {
+            this.scene.activeCamera = this._nonVRCamera;
+        }
     }
 
     /**
@@ -105,7 +95,7 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public enterXRAsync(sessionMode: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, renderTarget: WebXRRenderTarget = this.sessionManager.getWebXRRenderTarget()): Promise<WebXRSessionManager> {
         if (!this._supported) {
-            throw "WebXR not supported";
+            throw "WebXR not supported in this browser or environment";
         }
         this._setState(WebXRState.ENTERING_XR);
         let sessionCreationOptions: XRSessionInit = {
@@ -180,20 +170,24 @@ export class WebXRExperienceHelper implements IDisposable {
     }
 
     /**
-     * Disposes of the experience helper
+     * Exits XR mode and returns the scene to its original state
+     * @returns promise that resolves after xr mode has exited
      */
-    public dispose() {
-        this.camera.dispose();
-        this.onStateChangedObservable.clear();
-        this.onInitialXRPoseSetObservable.clear();
-        this.sessionManager.dispose();
-        if (this._nonVRCamera) {
-            this.scene.activeCamera = this._nonVRCamera;
-        }
+    public exitXRAsync() {
+        this._setState(WebXRState.EXITING_XR);
+        return this.sessionManager.exitXRAsync();
     }
 
     private _nonXRToXRCamera() {
         this.camera.setTransformationFromNonVRCamera(this._nonVRCamera!);
         this.onInitialXRPoseSetObservable.notifyObservers(this.camera);
     }
+
+    private _setState(val: WebXRState) {
+        if (this.state === val) {
+            return;
+        }
+        this.state = val;
+        this.onStateChangedObservable.notifyObservers(this.state);
+    }
 }

+ 105 - 107
src/XR/webXRFeaturesManager.ts

@@ -13,6 +13,7 @@ export interface IWebXRFeature extends IDisposable {
      * Should auto-attach be disabled?
      */
     disableAutoAttach: boolean;
+
     /**
      * Attach the feature to the session
      * Will usually be called by the features manager
@@ -35,10 +36,6 @@ export interface IWebXRFeature extends IDisposable {
  */
 export class WebXRFeatureName {
     /**
-     * The name of the hit test feature
-     */
-    public static HIT_TEST = "xr-hit-test";
-    /**
      * The name of the anchor system feature
      */
     public static ANCHOR_SYSTEM = "xr-anchor-system";
@@ -47,22 +44,25 @@ export class WebXRFeatureName {
      */
     public static BACKGROUND_REMOVER = "xr-background-remover";
     /**
-     * The name of the pointer selection feature
+     * The name of the hit test feature
      */
-    public static POINTER_SELECTION = "xr-controller-pointer-selection";
+    public static HIT_TEST = "xr-hit-test";
     /**
-     * The name of the teleportation feature
+     * physics impostors for xr controllers feature
      */
-    public static TELEPORTATION = "xr-controller-teleportation";
+    public static PHYSICS_CONTROLLERS = "xr-physics-controller";
     /**
      * The name of the plane detection feature
      */
     public static PLANE_DETECTION = "xr-plane-detection";
-
     /**
-     * physics impostors for xr controllers feature
+     * The name of the pointer selection feature
      */
-    public static PHYSICS_CONTROLLERS = "xr-physics-controller";
+    public static POINTER_SELECTION = "xr-controller-pointer-selection";
+    /**
+     * The name of the teleportation feature
+     */
+    public static TELEPORTATION = "xr-controller-teleportation";
 }
 
 /**
@@ -77,7 +77,6 @@ export type WebXRFeatureConstructor = (xrSessionManager: WebXRSessionManager, op
  * A feature can have a version that is defined by Babylon (and does not correspond with the webxr version).
  */
 export class WebXRFeaturesManager implements IDisposable {
-
     private static readonly _AvailableFeatures: {
         [name: string]: {
             stable: number;
@@ -86,6 +85,42 @@ export class WebXRFeaturesManager implements IDisposable {
         }
     } = {};
 
+    private _features: {
+        [name: string]: {
+            featureImplementation: IWebXRFeature,
+            version: number,
+            enabled: boolean
+        }
+    } = {};
+
+    /**
+     * constructs a new features manages.
+     *
+     * @param _xrSessionManager an instance of WebXRSessionManager
+     */
+    constructor(private _xrSessionManager: WebXRSessionManager) {
+        // when session starts / initialized - attach
+        this._xrSessionManager.onXRSessionInit.add(() => {
+            this.getEnabledFeatures().forEach((featureName) => {
+                const feature = this._features[featureName];
+                if (feature.enabled && !feature.featureImplementation.attached && !feature.featureImplementation.disableAutoAttach) {
+                    this.attachFeature(featureName);
+                }
+            });
+        });
+
+        // when session ends - detach
+        this._xrSessionManager.onXRSessionEnded.add(() => {
+            this.getEnabledFeatures().forEach((featureName) => {
+                const feature = this._features[featureName];
+                if (feature.enabled && feature.featureImplementation.attached) {
+                    // detach, but don't disable!
+                    this.detachFeature(featureName);
+                }
+            });
+        });
+    }
+
     /**
      * Used to register a module. After calling this function a developer can use this feature in the scene.
      * Mainly used internally.
@@ -126,6 +161,24 @@ export class WebXRFeaturesManager implements IDisposable {
     }
 
     /**
+     * Can be used to return the list of features currently registered
+     *
+     * @returns an Array of available features
+     */
+    public static GetAvailableFeatures() {
+        return Object.keys(this._AvailableFeatures);
+    }
+
+    /**
+     * Gets the versions available for a specific feature
+     * @param featureName the name of the feature
+     * @returns an array with the available versions
+     */
+    public static GetAvailableVersions(featureName: string) {
+        return Object.keys(this._AvailableFeatures[featureName]);
+    }
+
+    /**
      * Return the latest unstable version of this feature
      * @param featureName the name of the feature to search
      * @returns the version number. if not found will return -1
@@ -144,56 +197,53 @@ export class WebXRFeaturesManager implements IDisposable {
     }
 
     /**
-     * Can be used to return the list of features currently registered
-     *
-     * @returns an Array of available features
+     * Attach a feature to the current session. Mainly used when session started to start the feature effect.
+     * Can be used during a session to start a feature
+     * @param featureName the name of feature to attach
      */
-    public static GetAvailableFeatures() {
-        return Object.keys(this._AvailableFeatures);
+    public attachFeature(featureName: string) {
+        const feature = this._features[featureName];
+        if (feature && feature.enabled && !feature.featureImplementation.attached) {
+            feature.featureImplementation.attach();
+        }
     }
 
     /**
-     * Gets the versions available for a specific feature
-     * @param featureName the name of the feature
-     * @returns an array with the available versions
+     * Can be used inside a session or when the session ends to detach a specific feature
+     * @param featureName the name of the feature to detach
      */
-    public static GetAvailableVersions(featureName: string) {
-        return Object.keys(this._AvailableFeatures[featureName]);
+    public detachFeature(featureName: string) {
+        const feature = this._features[featureName];
+        if (feature && feature.featureImplementation.attached) {
+            feature.featureImplementation.detach();
+        }
     }
 
-    private _features: {
-        [name: string]: {
-            featureImplementation: IWebXRFeature,
-            version: number,
-            enabled: boolean
+    /**
+     * Used to disable an already-enabled feature
+     * The feature will be disposed and will be recreated once enabled.
+     * @param featureName the feature to disable
+     * @returns true if disable was successful
+     */
+    public disableFeature(featureName: string | { Name: string }): boolean {
+        const name = typeof featureName === 'string' ? featureName : featureName.Name;
+        const feature = this._features[name];
+        if (feature && feature.enabled) {
+            feature.enabled = false;
+            this.detachFeature(name);
+            feature.featureImplementation.dispose();
+            return true;
         }
-    } = {};
+        return false;
+    }
 
     /**
-     * constructs a new features manages.
-     *
-     * @param _xrSessionManager an instance of WebXRSessionManager
+     * dispose this features manager
      */
-    constructor(private _xrSessionManager: WebXRSessionManager) {
-        // when session starts / initialized - attach
-        this._xrSessionManager.onXRSessionInit.add(() => {
-            this.getEnabledFeatures().forEach((featureName) => {
-                const feature = this._features[featureName];
-                if (feature.enabled && !feature.featureImplementation.attached && !feature.featureImplementation.disableAutoAttach) {
-                    this.attachFeature(featureName);
-                }
-            });
-        });
-
-        // when session ends - detach
-        this._xrSessionManager.onXRSessionEnded.add(() => {
-            this.getEnabledFeatures().forEach((featureName) => {
-                const feature = this._features[featureName];
-                if (feature.enabled && feature.featureImplementation.attached) {
-                    // detach, but don't disable!
-                    this.detachFeature(featureName);
-                }
-            });
+    public dispose(): void {
+        this.getEnabledFeatures().forEach((feature) => {
+            this.disableFeature(feature);
+            this._features[feature].featureImplementation.dispose();
         });
     }
 
@@ -262,55 +312,6 @@ export class WebXRFeaturesManager implements IDisposable {
     }
 
     /**
-     * Used to disable an already-enabled feature
-     * The feature will be disposed and will be recreated once enabled.
-     * @param featureName the feature to disable
-     * @returns true if disable was successful
-     */
-    public disableFeature(featureName: string | { Name: string }): boolean {
-        const name = typeof featureName === 'string' ? featureName : featureName.Name;
-        const feature = this._features[name];
-        if (feature && feature.enabled) {
-            feature.enabled = false;
-            this.detachFeature(name);
-            feature.featureImplementation.dispose();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Attach a feature to the current session. Mainly used when session started to start the feature effect.
-     * Can be used during a session to start a feature
-     * @param featureName the name of feature to attach
-     */
-    public attachFeature(featureName: string) {
-        const feature = this._features[featureName];
-        if (feature && feature.enabled && !feature.featureImplementation.attached) {
-            feature.featureImplementation.attach();
-        }
-    }
-
-    /**
-     * Can be used inside a session or when the session ends to detach a specific feature
-     * @param featureName the name of the feature to detach
-     */
-    public detachFeature(featureName: string) {
-        const feature = this._features[featureName];
-        if (feature && feature.featureImplementation.attached) {
-            feature.featureImplementation.detach();
-        }
-    }
-
-    /**
-     * Get the list of enabled features
-     * @returns an array of enabled features
-     */
-    public getEnabledFeatures() {
-        return Object.keys(this._features);
-    }
-
-    /**
      * get the implementation of an enabled feature.
      * @param featureName the name of the feature to load
      * @returns the feature class, if found
@@ -320,13 +321,10 @@ export class WebXRFeaturesManager implements IDisposable {
     }
 
     /**
-     * dispose this features manager
+     * Get the list of enabled features
+     * @returns an array of enabled features
      */
-    dispose(): void {
-        this.getEnabledFeatures().forEach((feature) => {
-            this.disableFeature(feature);
-            this._features[feature].featureImplementation.dispose();
-        });
+    public getEnabledFeatures() {
+        return Object.keys(this._features);
     }
-
 }

+ 1 - 1
src/XR/webXRInput.ts

@@ -23,7 +23,7 @@ export interface IWebXRInputOptions {
     forceInputProfile?: string;
 
     /**
-     * Do not send a request to the controlle repository to load the profile.
+     * Do not send a request to the controller repository to load the profile.
      *
      * Instead, use the controllers available in babylon itself.
      */

+ 53 - 58
src/XR/webXRInputSource.ts

@@ -13,65 +13,60 @@ let idCount = 0;
  */
 export interface IWebXRControllerOptions {
     /**
-     * Force a specific controller type for this controller.
-     * This can be used when creating your own profile or when testing different controllers
+     * Should the controller mesh be animated when a user interacts with it
+     * The pressed buttons / thumbstick and touchpad animations will be disabled
      */
-    forceControllerProfile?: string;
-
+    disableMotionControllerAnimation?: boolean;
     /**
      * Do not load the controller mesh, in case a different mesh needs to be loaded.
      */
     doNotLoadControllerMesh?: boolean;
-
     /**
-     * Should the controller mesh be animated when a user interacts with it
-     * The pressed buttons / thumbstick and touchpad animations will be disabled
+     * Force a specific controller type for this controller.
+     * This can be used when creating your own profile or when testing different controllers
      */
-    disableMotionControllerAnimation?: boolean;
+    forceControllerProfile?: string;
 }
 
 /**
  * Represents an XR controller
  */
 export class WebXRInputSource {
+    private _tmpQuaternion = new Quaternion();
+    private _tmpVector = new Vector3();
+    private _uniqueId: string;
+
     /**
      * 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?: AbstractMesh;
     /**
-     * Pointer which can be used to select objects or attach a visible laser to
-     */
-    public pointer: AbstractMesh;
-    /**
      * If available, this is the gamepad object related to this controller.
      * Using this object it is possible to get click events and trackpad changes of the
      * webxr controller that is currently being used.
      */
     public motionController?: WebXRAbstractMotionController;
-
     /**
-     * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
+     * Event that fires when the controller is removed/disposed.
+     * The object provided as event data is this controller, after associated assets were disposed.
+     * uniqueId is still available.
      */
-    public onMotionControllerInitObservable = new Observable<WebXRAbstractMotionController>();
-
+    public onDisposeObservable = new Observable<WebXRInputSource>();
     /**
      * Will be triggered when the mesh associated with the motion controller is done loading.
      * It is also possible that this will never trigger (!) if no mesh was loaded, or if the developer decides to load a different mesh
      * A shortened version of controller -> motion controller -> on mesh loaded.
      */
     public onMeshLoadedObservable = new Observable<AbstractMesh>();
-
     /**
-     * Event that fires when the controller is removed/disposed.
-     * The object provided as event data is this controller, after associated assets were disposed.
-     * uniqueId is still available.
+     * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
      */
-    public onDisposeObservable = new Observable<WebXRInputSource>();
-
-    private _tmpQuaternion = new Quaternion();
-    private _tmpVector = new Vector3();
+    public onMotionControllerInitObservable = new Observable<WebXRAbstractMotionController>();
+    /**
+     * Pointer which can be used to select objects or attach a visible laser to
+     */
+    public pointer: AbstractMesh;
 
-    private _uniqueId: string;
     /**
      * Creates the controller
      * @see https://doc.babylonjs.com/how_to/webxr
@@ -121,6 +116,39 @@ export class WebXRInputSource {
     }
 
     /**
+     * Disposes of the object
+     */
+    public dispose() {
+        if (this.grip) {
+            this.grip.dispose();
+        }
+        if (this.motionController) {
+            this.motionController.dispose();
+        }
+        this.pointer.dispose();
+        this.onMotionControllerInitObservable.clear();
+        this.onMeshLoadedObservable.clear();
+        this.onDisposeObservable.notifyObservers(this);
+        this.onDisposeObservable.clear();
+    }
+
+    /**
+     * Gets a world space ray coming from the pointer or grip
+     * @param result the resulting ray
+     * @param gripIfAvailable use the grip mesh instead of the pointer, if available
+     */
+    public getWorldPointerRayToRef(result: Ray, gripIfAvailable: boolean = false) {
+        const object = gripIfAvailable && this.grip ? this.grip : this.pointer;
+        let worldMatrix = object.computeWorldMatrix();
+        worldMatrix.decompose(undefined, this._tmpQuaternion, undefined);
+        this._tmpVector.set(0, 0, 1);
+        this._tmpVector.rotateByQuaternionToRef(this._tmpQuaternion, this._tmpVector);
+        result.origin.copyFrom(object.absolutePosition);
+        result.direction.copyFrom(this._tmpVector);
+        result.length = 1000;
+    }
+
+    /**
      * Updates the controller pose based on the given XRFrame
      * @param xrFrame xr frame to update the pose with
      * @param referenceSpace reference space to use
@@ -157,37 +185,4 @@ export class WebXRInputSource {
             this.motionController.updateFromXRFrame(xrFrame);
         }
     }
-
-    /**
-     * Gets a world space ray coming from the pointer or grip
-     * @param result the resulting ray
-     * @param gripIfAvailable use the grip mesh instead of the pointer, if available
-     */
-    public getWorldPointerRayToRef(result: Ray, gripIfAvailable: boolean = false) {
-        const object = gripIfAvailable && this.grip ? this.grip : this.pointer;
-        let worldMatrix = object.computeWorldMatrix();
-        worldMatrix.decompose(undefined, this._tmpQuaternion, undefined);
-        this._tmpVector.set(0, 0, 1);
-        this._tmpVector.rotateByQuaternionToRef(this._tmpQuaternion, this._tmpVector);
-        result.origin.copyFrom(object.absolutePosition);
-        result.direction.copyFrom(this._tmpVector);
-        result.length = 1000;
-    }
-
-    /**
-     * Disposes of the object
-     */
-    dispose() {
-        if (this.grip) {
-            this.grip.dispose();
-        }
-        if (this.motionController) {
-            this.motionController.dispose();
-        }
-        this.pointer.dispose();
-        this.onMotionControllerInitObservable.clear();
-        this.onMeshLoadedObservable.clear();
-        this.onDisposeObservable.notifyObservers(this);
-        this.onDisposeObservable.clear();
-    }
 }

+ 40 - 43
src/XR/webXRManagedOutputCanvas.ts

@@ -8,22 +8,20 @@ import { WebXRSessionManager } from './webXRSessionManager';
  */
 export class WebXRManagedOutputCanvasOptions {
     /**
+     * An optional canvas in case you wish to create it yourself and provide it here.
+     * If not provided, a new canvas will be created
+     */
+    public canvasElement?: HTMLCanvasElement;
+    /**
      * Options for this XR Layer output
      */
     public canvasOptions?: XRWebGLLayerOptions;
-
     /**
      * CSS styling for a newly created canvas (if not provided)
      */
     public newCanvasCssStyle?: string;
 
     /**
-     * An optional canvas in case you wish to create it yourself and provide it here.
-     * If not provided, a new canvas will be created
-     */
-    public canvasElement?: HTMLCanvasElement;
-
-    /**
      * Get the default values of the configuration object
      * @returns default values of this configuration object
      */
@@ -47,9 +45,8 @@ export class WebXRManagedOutputCanvasOptions {
  * Creates a canvas that is added/removed from the webpage when entering/exiting XR
  */
 export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
-
-    private _engine: ThinEngine;
     private _canvas: Nullable<HTMLCanvasElement> = null;
+    private _engine: ThinEngine;
 
     /**
      * Rendering context of the canvas which can be used to display/mirror xr content
@@ -61,29 +58,6 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
     public xrLayer: Nullable<XRWebGLLayer> = null;
 
     /**
-     * Initializes the xr layer for the session
-     * @param xrSession xr session
-     * @returns a promise that will resolve once the XR Layer has been created
-     */
-    public initializeXRLayerAsync(xrSession: XRSession): Promise<XRWebGLLayer> {
-
-        const createLayer = () => {
-            return new XRWebGLLayer(xrSession, this.canvasContext, this._options.canvasOptions);
-        };
-
-        // support canvases without makeXRCompatible
-        if (!(this.canvasContext as any).makeXRCompatible) {
-            this.xrLayer = createLayer();
-            return Promise.resolve(this.xrLayer);
-        }
-
-        return (this.canvasContext as any).makeXRCompatible().then(() => {
-            this.xrLayer = createLayer();
-            return this.xrLayer;
-        });
-    }
-
-    /**
      * Initializes the canvas to be added/removed upon entering/exiting xr
      * @param _xrSessionManager The XR Session manager
      * @param _options optional configuration for this canvas output. defaults will be used if not provided
@@ -106,6 +80,7 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
             this._removeCanvas();
         });
     }
+
     /**
      * Disposes of the object
      */
@@ -114,18 +89,26 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
         this._setManagedOutputCanvas(null);
     }
 
-    private _setManagedOutputCanvas(canvas: Nullable<HTMLCanvasElement>) {
-        this._removeCanvas();
-        if (!canvas) {
-            this._canvas = null;
-            (this.canvasContext as any) = null;
-        } else {
-            this._canvas = canvas;
-            this.canvasContext = <any>this._canvas.getContext('webgl2');
-            if (!this.canvasContext) {
-                this.canvasContext = <any>this._canvas.getContext('webgl');
-            }
+    /**
+     * Initializes the xr layer for the session
+     * @param xrSession xr session
+     * @returns a promise that will resolve once the XR Layer has been created
+     */
+    public initializeXRLayerAsync(xrSession: XRSession): Promise<XRWebGLLayer> {
+        const createLayer = () => {
+            return new XRWebGLLayer(xrSession, this.canvasContext, this._options.canvasOptions);
+        };
+
+        // support canvases without makeXRCompatible
+        if (!(this.canvasContext as any).makeXRCompatible) {
+            this.xrLayer = createLayer();
+            return Promise.resolve(this.xrLayer);
         }
+
+        return (this.canvasContext as any).makeXRCompatible().then(() => {
+            this.xrLayer = createLayer();
+            return this.xrLayer;
+        });
     }
 
     private _addCanvas() {
@@ -139,4 +122,18 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
             document.body.removeChild(this._canvas);
         }
     }
+
+    private _setManagedOutputCanvas(canvas: Nullable<HTMLCanvasElement>) {
+        this._removeCanvas();
+        if (!canvas) {
+            this._canvas = null;
+            (this.canvasContext as any) = null;
+        } else {
+            this._canvas = canvas;
+            this.canvasContext = <any>this._canvas.getContext('webgl2');
+            if (!this.canvasContext) {
+                this.canvasContext = <any>this._canvas.getContext('webgl');
+            }
+        }
+    }
 }

+ 136 - 145
src/XR/webXRSessionManager.ts

@@ -28,11 +28,37 @@ class RenderTargetProvider implements IRenderTargetProvider {
  * @see https://doc.babylonjs.com/how_to/webxr
  */
 export class WebXRSessionManager implements IDisposable {
+    private _referenceSpace: XRReferenceSpace;
+    private _rttProvider: Nullable<IRenderTargetProvider>;
+    private _sessionEnded: boolean = false;
+    private _xrNavigator: any;
+    private baseLayer: Nullable<XRWebGLLayer> = null;
+
+    /**
+     * The base reference space from which the session started. good if you want to reset your
+     * reference space
+     */
+    public baseReferenceSpace: XRReferenceSpace;
+    /**
+     * Current XR frame
+     */
+    public currentFrame: Nullable<XRFrame>;
+    /** WebXR timestamp updated every frame */
+    public currentTimestamp: number = -1;
+    /**
+     * Used just in case of a failure to initialize an immersive session.
+     * The viewer reference space is compensated using this height, creating a kind of "viewer-floor" reference space
+     */
+    public defaultHeightCompensation = 1.7;
     /**
      * Fires every time a new xrFrame arrives which can be used to update the camera
      */
     public onXRFrameObservable: Observable<XRFrame> = new Observable<XRFrame>();
     /**
+     * Fires when the reference space changed
+     */
+    public onXRReferenceSpaceChanged: Observable<XRReferenceSpace> = new Observable();
+    /**
      * Fires when the xr session is ended either by the device or manually done
      */
     public onXRSessionEnded: Observable<any> = new Observable<any>();
@@ -40,24 +66,26 @@ export class WebXRSessionManager implements IDisposable {
      * Fires when the xr session is ended either by the device or manually done
      */
     public onXRSessionInit: Observable<XRSession> = new Observable<XRSession>();
-
-    /**
-     * Fires when the reference space changed
-     */
-    public onXRReferenceSpaceChanged: Observable<XRReferenceSpace> = new Observable();
-
     /**
      * Underlying xr session
      */
     public session: XRSession;
-
     /**
      * The viewer (head position) reference space. This can be used to get the XR world coordinates
      * or get the offset the player is currently at.
      */
     public viewerReferenceSpace: XRReferenceSpace;
 
-    private _referenceSpace: XRReferenceSpace;
+    /**
+     * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
+     * @param scene The scene which the session should be created for
+     */
+    constructor(
+        /** The scene which the session should be created for */
+        public scene: Scene
+    ) {
+    }
+
     /**
      * The current reference space used in this session. This reference space can constantly change!
      * It is mainly used to offset the camera's position.
@@ -73,41 +101,59 @@ export class WebXRSessionManager implements IDisposable {
         this._referenceSpace = newReferenceSpace;
         this.onXRReferenceSpaceChanged.notifyObservers(this._referenceSpace);
     }
+
     /**
-     * The base reference space from which the session started. good if you want to reset your
-     * reference space
+     * Disposes of the session manager
      */
-    public baseReferenceSpace: XRReferenceSpace;
+    public dispose() {
+        // disposing without leaving XR? Exit XR first
+        if (!this._sessionEnded) {
+            this.exitXRAsync();
+        }
+        this.onXRFrameObservable.clear();
+        this.onXRSessionEnded.clear();
+        this.onXRReferenceSpaceChanged.clear();
+        this.onXRSessionInit.clear();
+    }
 
     /**
-     * Used just in case of a failure to initialize an immersive session.
-     * The viewer reference space is compensated using this height, creating a kind of "viewer-floor" reference space
+     * Stops the xrSession and restores the render loop
+     * @returns Promise which resolves after it exits XR
      */
-    public defaultHeightCompensation = 1.7;
+    public exitXRAsync() {
+        if (this.session && !this._sessionEnded) {
+            return this.session.end().catch((e) => {
+                Logger.Warn("could not end XR session. It has ended already.");
+            });
+        }
+        return Promise.resolve();
+    }
 
     /**
-     * Current XR frame
+     * Gets the correct render target texture to be rendered this frame for this eye
+     * @param eye the eye for which to get the render target
+     * @returns the render target for the specified eye
      */
-    public currentFrame: Nullable<XRFrame>;
-
-    /** WebXR timestamp updated every frame */
-    public currentTimestamp: number = -1;
-
-    private _xrNavigator: any;
-    private baseLayer: Nullable<XRWebGLLayer> = null;
-    private _rttProvider: Nullable<IRenderTargetProvider>;
-
-    private _sessionEnded: boolean = false;
+    public getRenderTargetTextureForEye(eye: XREye): RenderTargetTexture {
+        return this._rttProvider!.getRenderTargetForEye(eye);
+    }
 
     /**
-     * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
-     * @param scene The scene which the session should be created for
+     * Creates a WebXRRenderTarget object for the XR session
+     * @param onStateChangedObservable optional, mechanism for enabling/disabling XR rendering canvas, used only on Web
+     * @param options optional options to provide when creating a new render target
+     * @returns a WebXR render target to which the session can render
      */
-    constructor(
-        /** The scene which the session should be created for */
-        public scene: Scene
-    ) {
-
+    public getWebXRRenderTarget(options?: WebXRManagedOutputCanvasOptions): WebXRRenderTarget {
+        const engine = this.scene.getEngine();
+        if (this._xrNavigator.xr.native) {
+            return this._xrNavigator.xr.getWebXRRenderTarget(engine);
+        }
+        else {
+            options = options || {};
+            options.canvasElement = engine.getRenderingCanvas() || undefined;
+            return new WebXRManagedOutputCanvas(this, options);
+        }
     }
 
     /**
@@ -116,7 +162,6 @@ export class WebXRSessionManager implements IDisposable {
      * @returns Promise which resolves after it is initialized
      */
     public initializeAsync(): Promise<void> {
-        Logger.Warn("The WebXR APIs are still under development and are subject to change in the future.");
         // Check if the browser supports webXR
         this._xrNavigator = navigator;
         if (!this._xrNavigator.xr) {
@@ -141,7 +186,7 @@ export class WebXRSessionManager implements IDisposable {
             this.session.addEventListener("end", () => {
                 const engine = this.scene.getEngine();
                 this._sessionEnded = true;
-                // Remove render target texture and notify frame obervers
+                // Remove render target texture and notify frame observers
                 this._rttProvider = null;
 
                 // Restore frame buffer to avoid clear on xr framebuffer after session end
@@ -157,34 +202,12 @@ export class WebXRSessionManager implements IDisposable {
     }
 
     /**
-     * Sets the reference space on the xr session
-     * @param referenceSpaceType space to set
-     * @returns a promise that will resolve once the reference space has been set
+     * Checks if a session would be supported for the creation options specified
+     * @param sessionMode session mode to check if supported eg. immersive-vr
+     * @returns A Promise that resolves to true if supported and false if not
      */
-    public setReferenceSpaceTypeAsync(referenceSpaceType: XRReferenceSpaceType = "local-floor"): Promise<XRReferenceSpace> {
-        return this.session.requestReferenceSpace(referenceSpaceType).then((referenceSpace: XRReferenceSpace) => {
-            return referenceSpace;
-        }, (rejectionReason) => {
-            Logger.Error("XR.requestReferenceSpace failed for the following reason: ");
-            Logger.Error(rejectionReason);
-            Logger.Log("Defaulting to universally-supported \"viewer\" reference space type.");
-
-            return this.session.requestReferenceSpace("viewer").then((referenceSpace: XRReferenceSpace) => {
-                const heightCompensation = new XRRigidTransform({ x: 0, y: -this.defaultHeightCompensation, z: 0 });
-                return referenceSpace.getOffsetReferenceSpace(heightCompensation);
-            }, (rejectionReason) => {
-                Logger.Error(rejectionReason);
-                throw "XR initialization failed: required \"viewer\" reference space type not supported.";
-            });
-        }).then((referenceSpace) => {
-            // initialize the base and offset (currently the same)
-            this.referenceSpace = this.baseReferenceSpace = referenceSpace;
-
-            this.session.requestReferenceSpace("viewer").then((referenceSpace: XRReferenceSpace) => {
-                this.viewerReferenceSpace = referenceSpace;
-            });
-            return this.referenceSpace;
-        });
+    public isSessionSupportedAsync(sessionMode: XRSessionMode): Promise<boolean> {
+        return WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
     }
 
     /**
@@ -195,18 +218,6 @@ export class WebXRSessionManager implements IDisposable {
     }
 
     /**
-     * Updates the render state of the session
-     * @param state state to set
-     * @returns a promise that resolves once the render state has been updated
-     */
-    public updateRenderStateAsync(state: XRRenderState) {
-        if (state.baseLayer) {
-            this.baseLayer = state.baseLayer;
-        }
-        return this.session.updateRenderState(state);
-    }
-
-    /**
      * Starts rendering to the xr layer
      */
     public runXRRenderLoop() {
@@ -234,7 +245,7 @@ export class WebXRSessionManager implements IDisposable {
                 return engine.createRenderTargetTexture({ width: width, height: height }, false);
             });
         } else {
-            // Create render target texture from xr's webgl render target
+            // Create render target texture from WebXR's webgl render target
             this._rttProvider = new RenderTargetProvider(WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!));
         }
 
@@ -244,51 +255,69 @@ export class WebXRSessionManager implements IDisposable {
     }
 
     /**
-     * Gets the correct render target texture to be rendered this frame for this eye
-     * @param eye the eye for which to get the render target
-     * @returns the render target for the specified eye
+     * Sets the reference space on the xr session
+     * @param referenceSpaceType space to set
+     * @returns a promise that will resolve once the reference space has been set
      */
-    public getRenderTargetTextureForEye(eye: XREye): RenderTargetTexture {
-        return this._rttProvider!.getRenderTargetForEye(eye);
-    }
+    public setReferenceSpaceTypeAsync(referenceSpaceType: XRReferenceSpaceType = "local-floor"): Promise<XRReferenceSpace> {
+        return this.session.requestReferenceSpace(referenceSpaceType).then((referenceSpace: XRReferenceSpace) => {
+            return referenceSpace;
+        }, (rejectionReason) => {
+            Logger.Error("XR.requestReferenceSpace failed for the following reason: ");
+            Logger.Error(rejectionReason);
+            Logger.Log("Defaulting to universally-supported \"viewer\" reference space type.");
 
-    /**
-     * Stops the xrSession and restores the renderloop
-     * @returns Promise which resolves after it exits XR
-     */
-    public exitXRAsync() {
-        if (this.session && !this._sessionEnded) {
-            return this.session.end().catch((e) => {
-                Logger.Warn("could not end XR session. It has ended already.");
+            return this.session.requestReferenceSpace("viewer").then((referenceSpace: XRReferenceSpace) => {
+                const heightCompensation = new XRRigidTransform({ x: 0, y: -this.defaultHeightCompensation, z: 0 });
+                return referenceSpace.getOffsetReferenceSpace(heightCompensation);
+            }, (rejectionReason) => {
+                Logger.Error(rejectionReason);
+                throw "XR initialization failed: required \"viewer\" reference space type not supported.";
             });
-        }
-        return Promise.resolve();
+        }).then((referenceSpace) => {
+            // initialize the base and offset (currently the same)
+            this.referenceSpace = this.baseReferenceSpace = referenceSpace;
+
+            this.session.requestReferenceSpace("viewer").then((referenceSpace: XRReferenceSpace) => {
+                this.viewerReferenceSpace = referenceSpace;
+            });
+            return this.referenceSpace;
+        });
     }
 
     /**
-     * Checks if a session would be supported for the creation options specified
-     * @param sessionMode session mode to check if supported eg. immersive-vr
-     * @returns A Promise that resolves to true if supported and false if not
+     * Updates the render state of the session
+     * @param state state to set
+     * @returns a promise that resolves once the render state has been updated
      */
-    public isSessionSupportedAsync(sessionMode: XRSessionMode): Promise<boolean> {
-        return WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
+    public updateRenderStateAsync(state: XRRenderState) {
+        if (state.baseLayer) {
+            this.baseLayer = state.baseLayer;
+        }
+        return this.session.updateRenderState(state);
     }
 
     /**
-     * Creates a WebXRRenderTarget object for the XR session
-     * @param onStateChangedObservable optional, mechanism for enabling/disabling XR rendering canvas, used only on Web
-     * @param options optional options to provide when creating a new render target
-     * @returns a WebXR render target to which the session can render
+     * Returns a promise that resolves with a boolean indicating if the provided session mode is supported by this browser
+     * @param sessionMode defines the session to test
+     * @returns a promise with boolean as final value
      */
-    public getWebXRRenderTarget(options?: WebXRManagedOutputCanvasOptions): WebXRRenderTarget {
-        const engine = this.scene.getEngine();
-        if (this._xrNavigator.xr.native) {
-            return this._xrNavigator.xr.getWebXRRenderTarget(engine);
+    public static IsSessionSupportedAsync(sessionMode: XRSessionMode): Promise<boolean> {
+        if (!(navigator as any).xr) {
+            return Promise.resolve(false);
         }
-        else {
-            options = options || {};
-            options.canvasElement = engine.getRenderingCanvas() || undefined;
-            return new WebXRManagedOutputCanvas(this, options);
+        // When the specs are final, remove supportsSession!
+        const functionToUse = (navigator as any).xr.isSessionSupported || (navigator as any).xr.supportsSession;
+        if (!functionToUse) {
+            return Promise.resolve(false);
+        } else {
+            return functionToUse.call((navigator as any).xr, sessionMode).then((result: boolean) => {
+                const returnValue = (typeof result === "undefined") ? true : result;
+                return Promise.resolve(returnValue);
+            }).catch((e: any) => {
+                Logger.Warn(e);
+                return Promise.resolve(false);
+            });
         }
     }
 
@@ -315,42 +344,4 @@ export class WebXRSessionManager implements IDisposable {
 
         return renderTargetTexture;
     }
-
-    /**
-     * Disposes of the session manager
-     */
-    public dispose() {
-        // disposing without leaving XR? Exit XR first
-        if (!this._sessionEnded) {
-            this.exitXRAsync();
-        }
-        this.onXRFrameObservable.clear();
-        this.onXRSessionEnded.clear();
-        this.onXRReferenceSpaceChanged.clear();
-        this.onXRSessionInit.clear();
-    }
-
-    /**
-     * Returns a promise that resolves with a boolean indicating if the provided session mode is supported by this browser
-     * @param sessionMode defines the session to test
-     * @returns a promise with boolean as final value
-     */
-    public static IsSessionSupportedAsync(sessionMode: XRSessionMode): Promise<boolean> {
-        if (!(navigator as any).xr) {
-            return Promise.resolve(false);
-        }
-        // When the specs are final, remove supportsSession!
-        const functionToUse = (navigator as any).xr.isSessionSupported || (navigator as any).xr.supportsSession;
-        if (!functionToUse) {
-            return Promise.resolve(false);
-        } else {
-            return functionToUse.call((navigator as any).xr, sessionMode).then((result: boolean) => {
-                const returnValue = (typeof result === "undefined") ? true : result;
-                return Promise.resolve(returnValue);
-            }).catch((e: any) => {
-                Logger.Warn(e);
-                return Promise.resolve(false);
-            });
-        }
-    }
 }

+ 0 - 0
tests/validation/ReferenceImages/glTFSerializerNegativeWorldMatrix.png


Some files were not shown because too many files changed in this diff