소스 검색

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

sebavan 5 년 전
부모
커밋
cc68a09136
76개의 변경된 파일10079개의 추가작업 그리고 2882개의 파일을 삭제
  1. 1002 322
      dist/preview release/babylon.d.ts
  2. 2 2
      dist/preview release/babylon.js
  3. 1620 252
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 2071 684
      dist/preview release/babylon.module.d.ts
  6. 1011 324
      dist/preview release/documentation.d.ts
  7. 1 1
      dist/preview release/glTF2Interface/package.json
  8. 2 2
      dist/preview release/gui/package.json
  9. 7 7
      dist/preview release/inspector/package.json
  10. 52 19
      dist/preview release/loaders/babylon.objFileLoader.js
  11. 1 1
      dist/preview release/loaders/babylon.objFileLoader.js.map
  12. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  13. 8 1
      dist/preview release/loaders/babylonjs.loaders.d.ts
  14. 52 19
      dist/preview release/loaders/babylonjs.loaders.js
  15. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  16. 2 2
      dist/preview release/loaders/babylonjs.loaders.min.js
  17. 26 10
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  18. 3 3
      dist/preview release/loaders/package.json
  19. 2 2
      dist/preview release/materialsLibrary/package.json
  20. 2 2
      dist/preview release/nodeEditor/package.json
  21. 1 1
      dist/preview release/package.json
  22. 1 1
      dist/preview release/packagesSizeBaseLine.json
  23. 2 2
      dist/preview release/postProcessesLibrary/package.json
  24. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  25. 2 2
      dist/preview release/serializers/babylon.glTF2Serializer.js
  26. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.js.map
  27. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  28. 1 1
      dist/preview release/serializers/babylonjs.serializers.d.ts
  29. 2 2
      dist/preview release/serializers/babylonjs.serializers.js
  30. 1 1
      dist/preview release/serializers/babylonjs.serializers.js.map
  31. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  32. 2 2
      dist/preview release/serializers/babylonjs.serializers.module.d.ts
  33. 3 3
      dist/preview release/serializers/package.json
  34. 2071 684
      dist/preview release/viewer/babylon.module.d.ts
  35. 114 82
      dist/preview release/viewer/babylon.viewer.js
  36. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  37. 26 10
      dist/preview release/viewer/babylonjs.loaders.module.d.ts
  38. 6 4
      dist/preview release/what's new.md
  39. 1 0
      loaders/src/OBJ/index.ts
  40. 210 0
      loaders/src/OBJ/mtlFileLoader.ts
  41. 11 205
      loaders/src/OBJ/objFileLoader.ts
  42. 1 1
      package.json
  43. 12 12
      src/Cameras/VR/vrExperienceHelper.ts
  44. 2 2
      src/Cameras/XR/index.ts
  45. 7 0
      src/Cameras/XR/motionController/index.ts
  46. 450 0
      src/Cameras/XR/motionController/webXRAbstractController.ts
  47. 241 0
      src/Cameras/XR/motionController/webXRControllerComponent.ts
  48. 80 0
      src/Cameras/XR/motionController/webXRGenericMotionController.ts
  49. 154 0
      src/Cameras/XR/motionController/webXRHTCViveMotionController.ts
  50. 264 0
      src/Cameras/XR/motionController/webXRMicrosoftMixedRealityController.ts
  51. 131 0
      src/Cameras/XR/motionController/webXRMotionControllerManager.ts
  52. 277 0
      src/Cameras/XR/motionController/webXROculusTouchMotionController.ts
  53. 9 12
      src/Cameras/XR/webXRCamera.ts
  54. 14 16
      src/Cameras/XR/webXRController.ts
  55. 0 77
      src/Cameras/XR/webXRControllerModelLoader.ts
  56. 6 6
      src/Cameras/XR/webXRControllerTeleportation.ts
  57. 9 10
      src/Cameras/XR/webXRDefaultExperience.ts
  58. 39 27
      src/Cameras/XR/webXRInput.ts
  59. 2 2
      src/Engines/thinEngine.ts
  60. 1 1
      src/Lights/directionalLight.ts
  61. 1 1
      src/Lights/hemisphericLight.ts
  62. 3 4
      src/Lights/light.ts
  63. 1 1
      src/Lights/pointLight.ts
  64. 1 1
      src/Lights/spotLight.ts
  65. 2 2
      src/Materials/Node/Blocks/Dual/lightBlock.ts
  66. 12 10
      src/Materials/Node/Blocks/Input/inputBlock.ts
  67. 1 1
      src/Materials/Node/nodeMaterialBlock.ts
  68. 1 1
      src/Materials/PBR/pbrBaseMaterial.ts
  69. 4 6
      src/Materials/materialHelper.ts
  70. 1 1
      src/Materials/standardMaterial.ts
  71. 5 5
      src/Shaders/ShadersInclude/lightFragment.fx
  72. 2 2
      src/Shaders/ShadersInclude/lightFragmentDeclaration.fx
  73. 1 1
      src/Shaders/ShadersInclude/lightUboDeclaration.fx
  74. 10 5
      tests/validation/config.json
  75. 0 5
      tests/validation/integration.js
  76. 7 7
      tests/validation/validation.js

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1002 - 322
dist/preview release/babylon.d.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
dist/preview release/babylon.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1620 - 252
dist/preview release/babylon.max.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/babylon.max.js.map


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2071 - 684
dist/preview release/babylon.module.d.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1011 - 324
dist/preview release/documentation.d.ts


+ 1 - 1
dist/preview release/glTF2Interface/package.json

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 2 - 2
dist/preview release/gui/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-gui",
     "description": "The Babylon.js GUI library is an extension you can use to generate interactive user interface. It is build on top of the DynamicTexture.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

+ 7 - 7
dist/preview release/inspector/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -29,12 +29,12 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17",
-        "babylonjs-gui": "4.1.0-beta.17",
-        "babylonjs-loaders": "4.1.0-beta.17",
-        "babylonjs-materials": "4.1.0-beta.17",
-        "babylonjs-serializers": "4.1.0-beta.17",
-        "babylonjs-gltf2interface": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18",
+        "babylonjs-gui": "4.1.0-beta.18",
+        "babylonjs-loaders": "4.1.0-beta.18",
+        "babylonjs-materials": "4.1.0-beta.18",
+        "babylonjs-serializers": "4.1.0-beta.18",
+        "babylonjs-gltf2interface": "4.1.0-beta.18"
     },
     "devDependencies": {
         "@types/react": "~16.7.3",

+ 52 - 19
dist/preview release/loaders/babylon.objFileLoader.js

@@ -136,38 +136,33 @@ module.exports = g;
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _objFileLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./objFileLoader */ "./OBJ/objFileLoader.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_0__["MTLFileLoader"]; });
+/* harmony import */ var _mtlFileLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return _mtlFileLoader__WEBPACK_IMPORTED_MODULE_0__["MTLFileLoader"]; });
 
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_0__["OBJFileLoader"]; });
+/* harmony import */ var _objFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./objFileLoader */ "./OBJ/objFileLoader.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_1__["OBJFileLoader"]; });
 
 
+
 
 
 /***/ }),
 
-/***/ "./OBJ/objFileLoader.ts":
+/***/ "./OBJ/mtlFileLoader.ts":
 /*!******************************!*\
-  !*** ./OBJ/objFileLoader.ts ***!
+  !*** ./OBJ/mtlFileLoader.ts ***!
   \******************************/
-/*! exports provided: MTLFileLoader, OBJFileLoader */
+/*! exports provided: MTLFileLoader */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return MTLFileLoader; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
 
 
 
-
-
-
-
-
-
 /**
  * Class reading and parsing the MTL file bundled with the obj file.
  */
@@ -373,11 +368,40 @@ var MTLFileLoader = /** @class */ (function () {
         else {
             url += value;
         }
-        return new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Texture"](url, scene, false, OBJFileLoader.INVERT_TEXTURE_Y);
+        return new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Texture"](url, scene, false, MTLFileLoader.INVERT_TEXTURE_Y);
     };
+    /**
+     * Invert Y-Axis of referenced textures on load
+     */
+    MTLFileLoader.INVERT_TEXTURE_Y = true;
     return MTLFileLoader;
 }());
 
+
+
+/***/ }),
+
+/***/ "./OBJ/objFileLoader.ts":
+/*!******************************!*\
+  !*** ./OBJ/objFileLoader.ts ***!
+  \******************************/
+/*! exports provided: OBJFileLoader */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
+
+
+
+
+
+
+
+
 /**
  * OBJ file type loader.
  * This is a babylon scene loader plugin.
@@ -432,6 +456,19 @@ var OBJFileLoader = /** @class */ (function () {
         this.facePattern5 = /f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
         this._meshLoadOptions = meshLoadOptions || OBJFileLoader.currentMeshLoadOptions;
     }
+    Object.defineProperty(OBJFileLoader, "INVERT_TEXTURE_Y", {
+        /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        get: function () {
+            return _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"].INVERT_TEXTURE_Y;
+        },
+        set: function (value) {
+            _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"].INVERT_TEXTURE_Y = value;
+        },
+        enumerable: true,
+        configurable: true
+    });
     Object.defineProperty(OBJFileLoader, "currentMeshLoadOptions", {
         get: function () {
             return {
@@ -587,7 +624,7 @@ var OBJFileLoader = /** @class */ (function () {
         var triangles = []; //Indices from new triangles coming from polygons
         var materialNameFromObj = ""; //The name of the current material
         var fileToLoad = ""; //The name of the mtlFile to load
-        var materialsFromMTLFile = new MTLFileLoader();
+        var materialsFromMTLFile = new _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"]();
         var objMeshName = ""; //The name of the current obj mesh
         var increment = 1; //Id for meshes created by the multimaterial
         var isFirstMaterial = true;
@@ -1180,10 +1217,6 @@ var OBJFileLoader = /** @class */ (function () {
      */
     OBJFileLoader.INVERT_Y = false;
     /**
-     * Invert Y-Axis of referenced textures on load
-     */
-    OBJFileLoader.INVERT_TEXTURE_Y = true;
-    /**
      * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
      */
     OBJFileLoader.IMPORT_VERTEX_COLORS = false;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.js.map


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.min.js


+ 8 - 1
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -2024,6 +2024,10 @@ declare module BABYLON {
      */
     export class MTLFileLoader {
         /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        static INVERT_TEXTURE_Y: boolean;
+        /**
          * All material loaded from the mtl will be set here
          */
         materials: StandardMaterial[];
@@ -2050,6 +2054,8 @@ declare module BABYLON {
          */
         private static _getTexture;
     }
+}
+declare module BABYLON {
     /**
      * Options for loading OBJ/MTL files
      */
@@ -2103,7 +2109,8 @@ declare module BABYLON {
         /**
          * Invert Y-Axis of referenced textures on load
          */
-        static INVERT_TEXTURE_Y: boolean;
+        static get INVERT_TEXTURE_Y(): boolean;
+        static set INVERT_TEXTURE_Y(value: boolean);
         /**
          * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
          */

+ 52 - 19
dist/preview release/loaders/babylonjs.loaders.js

@@ -365,38 +365,33 @@ module.exports = g;
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _objFileLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./objFileLoader */ "./OBJ/objFileLoader.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_0__["MTLFileLoader"]; });
+/* harmony import */ var _mtlFileLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return _mtlFileLoader__WEBPACK_IMPORTED_MODULE_0__["MTLFileLoader"]; });
 
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_0__["OBJFileLoader"]; });
+/* harmony import */ var _objFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./objFileLoader */ "./OBJ/objFileLoader.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return _objFileLoader__WEBPACK_IMPORTED_MODULE_1__["OBJFileLoader"]; });
 
 
+
 
 
 /***/ }),
 
-/***/ "./OBJ/objFileLoader.ts":
+/***/ "./OBJ/mtlFileLoader.ts":
 /*!******************************!*\
-  !*** ./OBJ/objFileLoader.ts ***!
+  !*** ./OBJ/mtlFileLoader.ts ***!
   \******************************/
-/*! exports provided: MTLFileLoader, OBJFileLoader */
+/*! exports provided: MTLFileLoader */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return MTLFileLoader; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
 
 
 
-
-
-
-
-
-
 /**
  * Class reading and parsing the MTL file bundled with the obj file.
  */
@@ -602,11 +597,40 @@ var MTLFileLoader = /** @class */ (function () {
         else {
             url += value;
         }
-        return new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Texture"](url, scene, false, OBJFileLoader.INVERT_TEXTURE_Y);
+        return new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__["Texture"](url, scene, false, MTLFileLoader.INVERT_TEXTURE_Y);
     };
+    /**
+     * Invert Y-Axis of referenced textures on load
+     */
+    MTLFileLoader.INVERT_TEXTURE_Y = true;
     return MTLFileLoader;
 }());
 
+
+
+/***/ }),
+
+/***/ "./OBJ/objFileLoader.ts":
+/*!******************************!*\
+  !*** ./OBJ/objFileLoader.ts ***!
+  \******************************/
+/*! exports provided: OBJFileLoader */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
+
+
+
+
+
+
+
+
 /**
  * OBJ file type loader.
  * This is a babylon scene loader plugin.
@@ -661,6 +685,19 @@ var OBJFileLoader = /** @class */ (function () {
         this.facePattern5 = /f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
         this._meshLoadOptions = meshLoadOptions || OBJFileLoader.currentMeshLoadOptions;
     }
+    Object.defineProperty(OBJFileLoader, "INVERT_TEXTURE_Y", {
+        /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        get: function () {
+            return _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"].INVERT_TEXTURE_Y;
+        },
+        set: function (value) {
+            _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"].INVERT_TEXTURE_Y = value;
+        },
+        enumerable: true,
+        configurable: true
+    });
     Object.defineProperty(OBJFileLoader, "currentMeshLoadOptions", {
         get: function () {
             return {
@@ -816,7 +853,7 @@ var OBJFileLoader = /** @class */ (function () {
         var triangles = []; //Indices from new triangles coming from polygons
         var materialNameFromObj = ""; //The name of the current material
         var fileToLoad = ""; //The name of the mtlFile to load
-        var materialsFromMTLFile = new MTLFileLoader();
+        var materialsFromMTLFile = new _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__["MTLFileLoader"]();
         var objMeshName = ""; //The name of the current obj mesh
         var increment = 1; //Id for meshes created by the multimaterial
         var isFirstMaterial = true;
@@ -1409,10 +1446,6 @@ var OBJFileLoader = /** @class */ (function () {
      */
     OBJFileLoader.INVERT_Y = false;
     /**
-     * Invert Y-Axis of referenced textures on load
-     */
-    OBJFileLoader.INVERT_TEXTURE_Y = true;
-    /**
      * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
      */
     OBJFileLoader.IMPORT_VERTEX_COLORS = false;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
dist/preview release/loaders/babylonjs.loaders.min.js


+ 26 - 10
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -2220,21 +2220,18 @@ declare module "babylonjs-loaders/glTF/index" {
     import * as GLTF2 from "babylonjs-loaders/glTF/2.0/index";
     export { GLTF1, GLTF2 };
 }
-declare module "babylonjs-loaders/OBJ/objFileLoader" {
-    import { Vector2 } from "babylonjs/Maths/math";
-    import { AnimationGroup } from "babylonjs/Animations/animationGroup";
-    import { Skeleton } from "babylonjs/Bones/skeleton";
-    import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
+declare module "babylonjs-loaders/OBJ/mtlFileLoader" {
     import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
-    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
-    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
-    import { AssetContainer } from "babylonjs/assetContainer";
     import { Scene } from "babylonjs/scene";
     /**
      * Class reading and parsing the MTL file bundled with the obj file.
      */
     export class MTLFileLoader {
         /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        static INVERT_TEXTURE_Y: boolean;
+        /**
          * All material loaded from the mtl will be set here
          */
         materials: StandardMaterial[];
@@ -2261,6 +2258,16 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
          */
         private static _getTexture;
     }
+}
+declare module "babylonjs-loaders/OBJ/objFileLoader" {
+    import { Vector2 } from "babylonjs/Maths/math";
+    import { AnimationGroup } from "babylonjs/Animations/animationGroup";
+    import { Skeleton } from "babylonjs/Bones/skeleton";
+    import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
+    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
+    import { AssetContainer } from "babylonjs/assetContainer";
+    import { Scene } from "babylonjs/scene";
     /**
      * Options for loading OBJ/MTL files
      */
@@ -2314,7 +2321,8 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
         /**
          * Invert Y-Axis of referenced textures on load
          */
-        static INVERT_TEXTURE_Y: boolean;
+        static get INVERT_TEXTURE_Y(): boolean;
+        static set INVERT_TEXTURE_Y(value: boolean);
         /**
          * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
          */
@@ -2455,6 +2463,7 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
     }
 }
 declare module "babylonjs-loaders/OBJ/index" {
+    export * from "babylonjs-loaders/OBJ/mtlFileLoader";
     export * from "babylonjs-loaders/OBJ/objFileLoader";
 }
 declare module "babylonjs-loaders/STL/stlFileLoader" {
@@ -4600,6 +4609,10 @@ declare module BABYLON {
      */
     export class MTLFileLoader {
         /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        static INVERT_TEXTURE_Y: boolean;
+        /**
          * All material loaded from the mtl will be set here
          */
         materials: StandardMaterial[];
@@ -4626,6 +4639,8 @@ declare module BABYLON {
          */
         private static _getTexture;
     }
+}
+declare module BABYLON {
     /**
      * Options for loading OBJ/MTL files
      */
@@ -4679,7 +4694,8 @@ declare module BABYLON {
         /**
          * Invert Y-Axis of referenced textures on load
          */
-        static INVERT_TEXTURE_Y: boolean;
+        static get INVERT_TEXTURE_Y(): boolean;
+        static set INVERT_TEXTURE_Y(value: boolean);
         /**
          * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
          */

+ 3 - 3
dist/preview release/loaders/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,8 +28,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "4.1.0-beta.17",
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs-gltf2interface": "4.1.0-beta.18",
+        "babylonjs": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/materialsLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-materials",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/nodeEditor/package.json

@@ -4,14 +4,14 @@
     },
     "name": "babylonjs-node-editor",
     "description": "The Babylon.js node material editor.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
     },
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18"
     },
     "files": [
         "babylon.nodeEditor.max.js.map",

+ 1 - 1
dist/preview release/package.json

@@ -7,7 +7,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"thinEngineOnly":111571,"engineOnly":148416,"sceneOnly":502112,"minGridMaterial":632499,"minStandardMaterial":757027}
+{"thinEngineOnly":111570,"engineOnly":148415,"sceneOnly":502177,"minGridMaterial":632519,"minStandardMaterial":757058}

+ 2 - 2
dist/preview release/postProcessesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/proceduralTexturesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-procedural-textures",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -1729,7 +1729,7 @@ var _Exporter = /** @class */ (function () {
         return true;
     };
     /**
-     * Lazy load a local engine with premultiplied alpha set to false
+     * Lazy load a local engine
      */
     _Exporter.prototype._getLocalEngine = function () {
         if (!this._localEngine) {
@@ -1737,7 +1737,7 @@ var _Exporter = /** @class */ (function () {
             localCanvas.id = "WriteCanvas";
             localCanvas.width = 2048;
             localCanvas.height = 2048;
-            this._localEngine = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Engine"](localCanvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
+            this._localEngine = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Engine"](localCanvas, true, { premultipliedAlpha: babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Tools"].IsSafari(), preserveDrawingBuffer: true });
             this._localEngine.setViewport(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Viewport"](0, 0, 1, 1));
         }
         return this._localEngine;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.js.map


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.d.ts

@@ -620,7 +620,7 @@ declare module BABYLON.GLTF2.Exporter {
          */
         static UnregisterExtension(name: string): boolean;
         /**
-         * Lazy load a local engine with premultiplied alpha set to false
+         * Lazy load a local engine
          */
         _getLocalEngine(): Engine;
         private reorderIndicesBasedOnPrimitiveMode;

+ 2 - 2
dist/preview release/serializers/babylonjs.serializers.js

@@ -1907,7 +1907,7 @@ var _Exporter = /** @class */ (function () {
         return true;
     };
     /**
-     * Lazy load a local engine with premultiplied alpha set to false
+     * Lazy load a local engine
      */
     _Exporter.prototype._getLocalEngine = function () {
         if (!this._localEngine) {
@@ -1915,7 +1915,7 @@ var _Exporter = /** @class */ (function () {
             localCanvas.id = "WriteCanvas";
             localCanvas.width = 2048;
             localCanvas.height = 2048;
-            this._localEngine = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Engine"](localCanvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
+            this._localEngine = new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Engine"](localCanvas, true, { premultipliedAlpha: babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Tools"].IsSafari(), preserveDrawingBuffer: true });
             this._localEngine.setViewport(new babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__["Viewport"](0, 0, 1, 1));
         }
         return this._localEngine;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.js.map


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.min.js


+ 2 - 2
dist/preview release/serializers/babylonjs.serializers.module.d.ts

@@ -661,7 +661,7 @@ declare module "babylonjs-serializers/glTF/2.0/glTFExporter" {
          */
         static UnregisterExtension(name: string): boolean;
         /**
-         * Lazy load a local engine with premultiplied alpha set to false
+         * Lazy load a local engine
          */
         _getLocalEngine(): Engine;
         private reorderIndicesBasedOnPrimitiveMode;
@@ -1867,7 +1867,7 @@ declare module BABYLON.GLTF2.Exporter {
          */
         static UnregisterExtension(name: string): boolean;
         /**
-         * Lazy load a local engine with premultiplied alpha set to false
+         * Lazy load a local engine
          */
         _getLocalEngine(): Engine;
         private reorderIndicesBasedOnPrimitiveMode;

+ 3 - 3
dist/preview release/serializers/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,8 +28,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-beta.17",
-        "babylonjs-gltf2interface": "4.1.0-beta.17"
+        "babylonjs": "4.1.0-beta.18",
+        "babylonjs-gltf2interface": "4.1.0-beta.18"
     },
     "engines": {
         "node": "*"

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2071 - 684
dist/preview release/viewer/babylon.module.d.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 114 - 82
dist/preview release/viewer/babylon.viewer.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


+ 26 - 10
dist/preview release/viewer/babylonjs.loaders.module.d.ts

@@ -2220,21 +2220,18 @@ declare module "babylonjs-loaders/glTF/index" {
     import * as GLTF2 from "babylonjs-loaders/glTF/2.0/index";
     export { GLTF1, GLTF2 };
 }
-declare module "babylonjs-loaders/OBJ/objFileLoader" {
-    import { Vector2 } from "babylonjs/Maths/math";
-    import { AnimationGroup } from "babylonjs/Animations/animationGroup";
-    import { Skeleton } from "babylonjs/Bones/skeleton";
-    import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
+declare module "babylonjs-loaders/OBJ/mtlFileLoader" {
     import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
-    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
-    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
-    import { AssetContainer } from "babylonjs/assetContainer";
     import { Scene } from "babylonjs/scene";
     /**
      * Class reading and parsing the MTL file bundled with the obj file.
      */
     export class MTLFileLoader {
         /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        static INVERT_TEXTURE_Y: boolean;
+        /**
          * All material loaded from the mtl will be set here
          */
         materials: StandardMaterial[];
@@ -2261,6 +2258,16 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
          */
         private static _getTexture;
     }
+}
+declare module "babylonjs-loaders/OBJ/objFileLoader" {
+    import { Vector2 } from "babylonjs/Maths/math";
+    import { AnimationGroup } from "babylonjs/Animations/animationGroup";
+    import { Skeleton } from "babylonjs/Bones/skeleton";
+    import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
+    import { ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
+    import { AssetContainer } from "babylonjs/assetContainer";
+    import { Scene } from "babylonjs/scene";
     /**
      * Options for loading OBJ/MTL files
      */
@@ -2314,7 +2321,8 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
         /**
          * Invert Y-Axis of referenced textures on load
          */
-        static INVERT_TEXTURE_Y: boolean;
+        static get INVERT_TEXTURE_Y(): boolean;
+        static set INVERT_TEXTURE_Y(value: boolean);
         /**
          * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
          */
@@ -2455,6 +2463,7 @@ declare module "babylonjs-loaders/OBJ/objFileLoader" {
     }
 }
 declare module "babylonjs-loaders/OBJ/index" {
+    export * from "babylonjs-loaders/OBJ/mtlFileLoader";
     export * from "babylonjs-loaders/OBJ/objFileLoader";
 }
 declare module "babylonjs-loaders/STL/stlFileLoader" {
@@ -4600,6 +4609,10 @@ declare module BABYLON {
      */
     export class MTLFileLoader {
         /**
+         * Invert Y-Axis of referenced textures on load
+         */
+        static INVERT_TEXTURE_Y: boolean;
+        /**
          * All material loaded from the mtl will be set here
          */
         materials: StandardMaterial[];
@@ -4626,6 +4639,8 @@ declare module BABYLON {
          */
         private static _getTexture;
     }
+}
+declare module BABYLON {
     /**
      * Options for loading OBJ/MTL files
      */
@@ -4679,7 +4694,8 @@ declare module BABYLON {
         /**
          * Invert Y-Axis of referenced textures on load
          */
-        static INVERT_TEXTURE_Y: boolean;
+        static get INVERT_TEXTURE_Y(): boolean;
+        static set INVERT_TEXTURE_Y(value: boolean);
         /**
          * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
          */

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

@@ -48,7 +48,7 @@
 - Added `RawTexture2DArray` to enable use of WebGL2 2D array textures by custom shaders ([atg](https://github.com/atg))
 - Added multiview support for the shader material (and the line-mesh class) ([RaananW](https://github.com/RaananW/))
 - Added various (interpolation) functions to Path3D, also `alignTangentsWithPath`, `slice`, `getClosestPositionTo` ([Poolminer](https://github.com/Poolminer/))
-- Allow setting of `BABYLON.Basis.JSModuleURL` and `BABYLON.Basis.WasmModuleURL`, for hosting the Basis transcoder locally ([JasonAyre])(https://github.com/jasonyre))
+- Allow setting of `BABYLON.Basis.JSModuleURL` and `BABYLON.Basis.WasmModuleURL`, for hosting the Basis transcoder locally ([JasonAyre](https://github.com/jasonyre))
 - PNG support for browsers not supporting SVG ([RaananW](https://github.com/RaananW/))
 - Device orientation event permissions for iOS 13+ ([RaananW](https://github.com/RaananW/))
 - Added `DirectionalLight.autoCalcShadowZBounds` to automatically compute the `shadowMinZ` and `shadowMaxZ` values ([Popov72](https://github.com/Popov72))
@@ -181,7 +181,8 @@
 - WebXR teleportation can now be disabled after initialized ([RaananW](https://github.com/RaananW/))
 - New Features Manager for WebXR features ([RaananW](https://github.com/RaananW/))
 - New features - Plane detection, Hit Test, Background remover ([RaananW](https://github.com/RaananW/))
-- Camera's API works as expected (position, rotationQuaternion, world matrix etc') ([#7239](https://github.com/BabylonJS/Babylon.js/issues/7239)) ([RaananW](https://github.com/RaananW/))
+- Camera's API is Babylon-conform (position, rotationQuaternion, world matrix, direction etc') ([#7239](https://github.com/BabylonJS/Babylon.js/issues/7239)) ([RaananW](https://github.com/RaananW/))
+- XR Input now using standard profiles and completely separated from the gamepad class ([#7348](https://github.com/BabylonJS/Babylon.js/issues/7348)) ([RaananW](https://github.com/RaananW/))
 
 ### Ray
 
@@ -215,7 +216,7 @@
 ### Serializers
 
 - Added support for `AnimationGroup` serialization ([Drigax](https://github.com/drigax/))
-- Expanded animation group serialization to include all targeted TransformNodes ([Drigax]https://github.com/drigax/)
+- Expanded animation group serialization to include all targeted TransformNodes ([Drigax](https://github.com/drigax/))
 
 ### Documentation
 
@@ -282,4 +283,5 @@
 - The glTF loader extensions that map to glTF 2.0 extensions will now be disabled if the extension is not present in `extensionsUsed`. ([bghgary](https://github.com/bghgary))
 - The STL loader does not create light or camera automatically, please use `scene.createDefaultCameraOrLight();` in your code [Sebavan](https://github.com/sebavan/)
 - The glTF2 exporter extension no longer ignores childless empty nodes.([drigax](https://github.com/drigax))
-- Default culling strategy changed to CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY ([Deltakosh](https://github.com/deltakosh/))
+- Default culling strategy changed to CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY ([Deltakosh](https://github.com/deltakosh/))
+- `MaterialHelper.BindLight` and `MaterialHelper.BindLights` do not need the usePhysicalLight anymore ([Sebavan](https://github.com/sebavan/))

+ 1 - 0
loaders/src/OBJ/index.ts

@@ -1 +1,2 @@
+export * from "./mtlFileLoader";
 export * from "./objFileLoader";

+ 210 - 0
loaders/src/OBJ/mtlFileLoader.ts

@@ -0,0 +1,210 @@
+import { Nullable } from "babylonjs/types";
+import { Color3 } from "babylonjs/Maths/math";
+import { Texture } from "babylonjs/Materials/Textures/texture";
+import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
+
+import { Scene } from "babylonjs/scene";
+/**
+ * Class reading and parsing the MTL file bundled with the obj file.
+ */
+export class MTLFileLoader {
+   /**
+    * Invert Y-Axis of referenced textures on load
+    */
+   public static INVERT_TEXTURE_Y = true;
+
+    /**
+     * All material loaded from the mtl will be set here
+     */
+    public materials: StandardMaterial[] = [];
+
+    /**
+     * This function will read the mtl file and create each material described inside
+     * This function could be improve by adding :
+     * -some component missing (Ni, Tf...)
+     * -including the specific options available
+     *
+     * @param scene defines the scene the material will be created in
+     * @param data defines the mtl data to parse
+     * @param rootUrl defines the rooturl to use in order to load relative dependencies
+     */
+    public parseMTL(scene: Scene, data: string | ArrayBuffer, rootUrl: string): void {
+        if (data instanceof ArrayBuffer) {
+            return;
+        }
+
+        //Split the lines from the file
+        var lines = data.split('\n');
+        //Space char
+        var delimiter_pattern = /\s+/;
+        //Array with RGB colors
+        var color: number[];
+        //New material
+        var material: Nullable<StandardMaterial> = null;
+
+        //Look at each line
+        for (var i = 0; i < lines.length; i++) {
+            var line = lines[i].trim();
+
+            // Blank line or comment
+            if (line.length === 0 || line.charAt(0) === '#') {
+                continue;
+            }
+
+            //Get the first parameter (keyword)
+            var pos = line.indexOf(' ');
+            var key = (pos >= 0) ? line.substring(0, pos) : line;
+            key = key.toLowerCase();
+
+            //Get the data following the key
+            var value: string = (pos >= 0) ? line.substring(pos + 1).trim() : "";
+
+            //This mtl keyword will create the new material
+            if (key === "newmtl") {
+                //Check if it is the first material.
+                // Materials specifications are described after this keyword.
+                if (material) {
+                    //Add the previous material in the material array.
+                    this.materials.push(material);
+                }
+                //Create a new material.
+                // value is the name of the material read in the mtl file
+                material = new StandardMaterial(value, scene);
+            } else if (key === "kd" && material) {
+                // Diffuse color (color under white light) using RGB values
+
+                //value  = "r g b"
+                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
+                //color = [r,g,b]
+                //Set tghe color into the material
+                material.diffuseColor = Color3.FromArray(color);
+            } else if (key === "ka" && material) {
+                // Ambient color (color under shadow) using RGB values
+
+                //value = "r g b"
+                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
+                //color = [r,g,b]
+                //Set tghe color into the material
+                material.ambientColor = Color3.FromArray(color);
+            } else if (key === "ks" && material) {
+                // Specular color (color when light is reflected from shiny surface) using RGB values
+
+                //value = "r g b"
+                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
+                //color = [r,g,b]
+                //Set the color into the material
+                material.specularColor = Color3.FromArray(color);
+            } else if (key === "ke" && material) {
+                // Emissive color using RGB values
+                color = value.split(delimiter_pattern, 3).map(parseFloat);
+                material.emissiveColor = Color3.FromArray(color);
+            } else if (key === "ns" && material) {
+
+                //value = "Integer"
+                material.specularPower = parseFloat(value);
+            } else if (key === "d" && material) {
+                //d is dissolve for current material. It mean alpha for BABYLON
+                material.alpha = parseFloat(value);
+
+                //Texture
+                //This part can be improved by adding the possible options of texture
+            } else if (key === "map_ka" && material) {
+                // ambient texture map with a loaded image
+                //We must first get the folder of the image
+                material.ambientTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
+            } else if (key === "map_kd" && material) {
+                // Diffuse texture map with a loaded image
+                material.diffuseTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
+            } else if (key === "map_ks" && material) {
+                // Specular texture map with a loaded image
+                //We must first get the folder of the image
+                material.specularTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
+            } else if (key === "map_ns") {
+                //Specular
+                //Specular highlight component
+                //We must first get the folder of the image
+                //
+                //Not supported by BABYLON
+                //
+                //    continue;
+            } else if (key === "map_bump" && material) {
+                //The bump texture
+                material.bumpTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
+            } else if (key === "map_d" && material) {
+                // The dissolve of the material
+                material.opacityTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
+
+                //Options for illumination
+            } else if (key === "illum") {
+                //Illumination
+                if (value === "0") {
+                    //That mean Kd == Kd
+                } else if (value === "1") {
+                    //Color on and Ambient on
+                } else if (value === "2") {
+                    //Highlight on
+                } else if (value === "3") {
+                    //Reflection on and Ray trace on
+                } else if (value === "4") {
+                    //Transparency: Glass on, Reflection: Ray trace on
+                } else if (value === "5") {
+                    //Reflection: Fresnel on and Ray trace on
+                } else if (value === "6") {
+                    //Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
+                } else if (value === "7") {
+                    //Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
+                } else if (value === "8") {
+                    //Reflection on and Ray trace off
+                } else if (value === "9") {
+                    //Transparency: Glass on, Reflection: Ray trace off
+                } else if (value === "10") {
+                    //Casts shadows onto invisible surfaces
+                }
+            } else {
+                // console.log("Unhandled expression at line : " + i +'\n' + "with value : " + line);
+            }
+        }
+        //At the end of the file, add the last material
+        if (material) {
+            this.materials.push(material);
+        }
+    }
+
+    /**
+     * Gets the texture for the material.
+     *
+     * If the material is imported from input file,
+     * We sanitize the url to ensure it takes the textre from aside the material.
+     *
+     * @param rootUrl The root url to load from
+     * @param value The value stored in the mtl
+     * @return The Texture
+     */
+    private static _getTexture(rootUrl: string, value: string, scene: Scene): Nullable<Texture> {
+        if (!value) {
+            return null;
+        }
+
+        var url = rootUrl;
+        // Load from input file.
+        if (rootUrl === "file:") {
+            var lastDelimiter = value.lastIndexOf("\\");
+            if (lastDelimiter === -1) {
+                lastDelimiter = value.lastIndexOf("/");
+            }
+
+            if (lastDelimiter > -1) {
+                url += value.substr(lastDelimiter + 1);
+            }
+            else {
+                url += value;
+            }
+        }
+        // Not from input file.
+        else {
+            url += value;
+        }
+
+        return new Texture(url, scene, false, MTLFileLoader.INVERT_TEXTURE_Y);
+    }
+}

+ 11 - 205
loaders/src/OBJ/objFileLoader.ts

@@ -1,13 +1,11 @@
-import { Nullable, FloatArray, IndicesArray } from "babylonjs/types";
-import { Vector3, Vector2, Color3, Color4 } from "babylonjs/Maths/math";
+import { FloatArray, IndicesArray } from "babylonjs/types";
+import { Vector3, Vector2, Color4 } from "babylonjs/Maths/math";
 import { Tools } from "babylonjs/Misc/tools";
 import { VertexData } from "babylonjs/Meshes/mesh.vertexData";
 import { Geometry } from "babylonjs/Meshes/geometry";
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 import { Skeleton } from "babylonjs/Bones/skeleton";
 import { IParticleSystem } from "babylonjs/Particles/IParticleSystem";
-import { Texture } from "babylonjs/Materials/Textures/texture";
-import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
 import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
 import { Mesh } from "babylonjs/Meshes/mesh";
 import { SceneLoader, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneLoaderPluginFactory, ISceneLoaderPlugin } from "babylonjs/Loading/sceneLoader";
@@ -15,206 +13,7 @@ import { SceneLoader, ISceneLoaderPluginAsync, SceneLoaderProgressEvent, ISceneL
 import { AssetContainer } from "babylonjs/assetContainer";
 import { Scene } from "babylonjs/scene";
 import { WebRequest } from 'babylonjs/Misc/webRequest';
-/**
- * Class reading and parsing the MTL file bundled with the obj file.
- */
-export class MTLFileLoader {
-
-    /**
-     * All material loaded from the mtl will be set here
-     */
-    public materials: StandardMaterial[] = [];
-
-    /**
-     * This function will read the mtl file and create each material described inside
-     * This function could be improve by adding :
-     * -some component missing (Ni, Tf...)
-     * -including the specific options available
-     *
-     * @param scene defines the scene the material will be created in
-     * @param data defines the mtl data to parse
-     * @param rootUrl defines the rooturl to use in order to load relative dependencies
-     */
-    public parseMTL(scene: Scene, data: string | ArrayBuffer, rootUrl: string): void {
-        if (data instanceof ArrayBuffer) {
-            return;
-        }
-
-        //Split the lines from the file
-        var lines = data.split('\n');
-        //Space char
-        var delimiter_pattern = /\s+/;
-        //Array with RGB colors
-        var color: number[];
-        //New material
-        var material: Nullable<StandardMaterial> = null;
-
-        //Look at each line
-        for (var i = 0; i < lines.length; i++) {
-            var line = lines[i].trim();
-
-            // Blank line or comment
-            if (line.length === 0 || line.charAt(0) === '#') {
-                continue;
-            }
-
-            //Get the first parameter (keyword)
-            var pos = line.indexOf(' ');
-            var key = (pos >= 0) ? line.substring(0, pos) : line;
-            key = key.toLowerCase();
-
-            //Get the data following the key
-            var value: string = (pos >= 0) ? line.substring(pos + 1).trim() : "";
-
-            //This mtl keyword will create the new material
-            if (key === "newmtl") {
-                //Check if it is the first material.
-                // Materials specifications are described after this keyword.
-                if (material) {
-                    //Add the previous material in the material array.
-                    this.materials.push(material);
-                }
-                //Create a new material.
-                // value is the name of the material read in the mtl file
-                material = new StandardMaterial(value, scene);
-            } else if (key === "kd" && material) {
-                // Diffuse color (color under white light) using RGB values
-
-                //value  = "r g b"
-                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
-                //color = [r,g,b]
-                //Set tghe color into the material
-                material.diffuseColor = Color3.FromArray(color);
-            } else if (key === "ka" && material) {
-                // Ambient color (color under shadow) using RGB values
-
-                //value = "r g b"
-                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
-                //color = [r,g,b]
-                //Set tghe color into the material
-                material.ambientColor = Color3.FromArray(color);
-            } else if (key === "ks" && material) {
-                // Specular color (color when light is reflected from shiny surface) using RGB values
-
-                //value = "r g b"
-                color = <number[]>value.split(delimiter_pattern, 3).map(parseFloat);
-                //color = [r,g,b]
-                //Set the color into the material
-                material.specularColor = Color3.FromArray(color);
-            } else if (key === "ke" && material) {
-                // Emissive color using RGB values
-                color = value.split(delimiter_pattern, 3).map(parseFloat);
-                material.emissiveColor = Color3.FromArray(color);
-            } else if (key === "ns" && material) {
-
-                //value = "Integer"
-                material.specularPower = parseFloat(value);
-            } else if (key === "d" && material) {
-                //d is dissolve for current material. It mean alpha for BABYLON
-                material.alpha = parseFloat(value);
-
-                //Texture
-                //This part can be improved by adding the possible options of texture
-            } else if (key === "map_ka" && material) {
-                // ambient texture map with a loaded image
-                //We must first get the folder of the image
-                material.ambientTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
-            } else if (key === "map_kd" && material) {
-                // Diffuse texture map with a loaded image
-                material.diffuseTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
-            } else if (key === "map_ks" && material) {
-                // Specular texture map with a loaded image
-                //We must first get the folder of the image
-                material.specularTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
-            } else if (key === "map_ns") {
-                //Specular
-                //Specular highlight component
-                //We must first get the folder of the image
-                //
-                //Not supported by BABYLON
-                //
-                //    continue;
-            } else if (key === "map_bump" && material) {
-                //The bump texture
-                material.bumpTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
-            } else if (key === "map_d" && material) {
-                // The dissolve of the material
-                material.opacityTexture = MTLFileLoader._getTexture(rootUrl, value, scene);
-
-                //Options for illumination
-            } else if (key === "illum") {
-                //Illumination
-                if (value === "0") {
-                    //That mean Kd == Kd
-                } else if (value === "1") {
-                    //Color on and Ambient on
-                } else if (value === "2") {
-                    //Highlight on
-                } else if (value === "3") {
-                    //Reflection on and Ray trace on
-                } else if (value === "4") {
-                    //Transparency: Glass on, Reflection: Ray trace on
-                } else if (value === "5") {
-                    //Reflection: Fresnel on and Ray trace on
-                } else if (value === "6") {
-                    //Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
-                } else if (value === "7") {
-                    //Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
-                } else if (value === "8") {
-                    //Reflection on and Ray trace off
-                } else if (value === "9") {
-                    //Transparency: Glass on, Reflection: Ray trace off
-                } else if (value === "10") {
-                    //Casts shadows onto invisible surfaces
-                }
-            } else {
-                // console.log("Unhandled expression at line : " + i +'\n' + "with value : " + line);
-            }
-        }
-        //At the end of the file, add the last material
-        if (material) {
-            this.materials.push(material);
-        }
-    }
-
-    /**
-     * Gets the texture for the material.
-     *
-     * If the material is imported from input file,
-     * We sanitize the url to ensure it takes the textre from aside the material.
-     *
-     * @param rootUrl The root url to load from
-     * @param value The value stored in the mtl
-     * @return The Texture
-     */
-    private static _getTexture(rootUrl: string, value: string, scene: Scene): Nullable<Texture> {
-        if (!value) {
-            return null;
-        }
-
-        var url = rootUrl;
-        // Load from input file.
-        if (rootUrl === "file:") {
-            var lastDelimiter = value.lastIndexOf("\\");
-            if (lastDelimiter === -1) {
-                lastDelimiter = value.lastIndexOf("/");
-            }
-
-            if (lastDelimiter > -1) {
-                url += value.substr(lastDelimiter + 1);
-            }
-            else {
-                url += value;
-            }
-        }
-        // Not from input file.
-        else {
-            url += value;
-        }
-
-        return new Texture(url, scene, false, OBJFileLoader.INVERT_TEXTURE_Y);
-    }
-}
+import { MTLFileLoader } from './mtlFileLoader';
 
 type MeshObject = {
     name: string;
@@ -281,7 +80,14 @@ export class OBJFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPlugi
     /**
      * Invert Y-Axis of referenced textures on load
      */
-    public static INVERT_TEXTURE_Y = true;
+    public static get INVERT_TEXTURE_Y() {
+        return MTLFileLoader.INVERT_TEXTURE_Y;
+    }
+
+    public static set INVERT_TEXTURE_Y(value: boolean) {
+        MTLFileLoader.INVERT_TEXTURE_Y = value;
+    }
+
     /**
      * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
      */

+ 1 - 1
package.json

@@ -7,7 +7,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.1.0-beta.17",
+    "version": "4.1.0-beta.18",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 12 - 12
src/Cameras/VR/vrExperienceHelper.ts

@@ -844,18 +844,18 @@ export class VRExperienceHelper {
                         });
 
                         this.xr.input.onControllerAddedObservable.add((controller) => {
-                            var webVRController = controller.gamepadController;
-                            if (webVRController) {
-                                var localController = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
-
-                                if (controller.inputSource.handedness === "right" || (this._leftController && this._leftController.webVRController != webVRController)) {
-                                    this._rightController = localController;
-                                } else {
-                                    this._leftController = localController;
-                                }
-
-                                this._tryEnableInteractionOnController(localController);
-                            }
+                            // var webVRController = controller.gamepadController;
+                            // if (webVRController) {
+                            //     var localController = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
+
+                            //     if (controller.inputSource.handedness === "right" || (this._leftController)) {
+                            //         this._rightController = localController;
+                            //     } else {
+                            //         this._leftController = localController;
+                            //     }
+
+                            //     this._tryEnableInteractionOnController(localController);
+                            // }
                         });
                     });
                 } else {

+ 2 - 2
src/Cameras/XR/index.ts

@@ -4,11 +4,11 @@ export * from "./webXRExperienceHelper";
 export * from "./webXRInput";
 export * from "./webXRControllerTeleportation";
 export * from "./webXRControllerPointerSelection";
-export * from "./webXRControllerModelLoader";
 export * from "./webXRController";
 export * from "./webXRManagedOutputCanvas";
 export * from "./webXRTypes";
 export * from "./webXRSessionManager";
 export * from "./webXRDefaultExperience";
 export * from "./webXRFeaturesManager";
-export * from "./features/index";
+export * from "./features/index";
+export * from "./motionController/index";

+ 7 - 0
src/Cameras/XR/motionController/index.ts

@@ -0,0 +1,7 @@
+export * from "./webXRAbstractController";
+export * from "./webXRControllerComponent";
+export * from "./webXRGenericMotionController";
+export * from "./webXRMicrosoftMixedRealityController";
+export * from "./webXRMotionControllerManager";
+export * from "./webXROculusTouchMotionController";
+export * from "./webXRHTCViveMotionController";

+ 450 - 0
src/Cameras/XR/motionController/webXRAbstractController.ts

@@ -0,0 +1,450 @@
+import { IDisposable, Scene } from '../../../scene';
+import { WebXRControllerComponent } from './webXRControllerComponent';
+import { Observable } from '../../../Misc/observable';
+import { Logger } from '../../../Misc/logger';
+import { SceneLoader } from '../../../Loading/sceneLoader';
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { Nullable } from '../../../types';
+import { Quaternion, Vector3 } from '../../../Maths/math.vector';
+import { Mesh } from '../../../Meshes/mesh';
+
+/**
+ * Handness type in xrInput profiles. These can be used to define layouts in the Layout Map.
+ */
+export type MotionControllerHandness = "none" | "left" | "right" | "left-right" | "left-right-none";
+/**
+ * The type of components available in motion controllers.
+ * This is not the name of the component.
+ */
+export type MotionControllerComponentType = "trigger" | "squeeze" | "touchpad" | "thumbstick" | "button";
+
+/**
+ * The schema of motion controller layout.
+ * No object will be initialized using this interface
+ * This is used just to define the profile.
+ */
+export interface IMotionControllerLayout {
+    /**
+     * Defines the main button component id
+     */
+    selectComponentId: string;
+    /**
+     * Available components (unsorted)
+     */
+    components: {
+        /**
+         * A map of component Ids
+         */
+        [componentId: string]: {
+            /**
+             * The type of input the component outputs
+             */
+            type: MotionControllerComponentType;
+        }
+    };
+    /**
+     * An optional gamepad object. If no gamepad object is not defined, no models will be loaded
+     */
+    gamepad?: {
+        /**
+         * Is the mapping based on the xr-standard defined here:
+         * https://www.w3.org/TR/webxr-gamepads-module-1/#xr-standard-gamepad-mapping
+         */
+        mapping: "" | "xr-standard";
+        /**
+         * The buttons available in this input in the right order
+         * index of this button will be the index in the gamepadObject.buttons array
+         * correlates to the componentId in components
+         */
+        buttons: Array<string | null>;
+        /**
+         * Definition of the axes of the gamepad input, sorted
+         * Correlates to componentIds in the components map
+         */
+        axes: Array<{
+            /**
+             * The component id that the axis correlates to
+             */
+            componentId: string;
+            /**
+             * X or Y Axis
+             */
+            axis: "x-axis" | "y-axis";
+        } | null>;
+    };
+}
+
+/**
+ * A definition for the layout map in the input profile
+ */
+export interface IMotionControllerLayoutMap {
+    /**
+     * Layouts with handness type as a key
+     */
+    [handness: string /* handness */]: IMotionControllerLayout;
+}
+
+/**
+ * The XR Input profile schema
+ * Profiles can be found here:
+ * https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry/profiles
+ */
+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[];
+    /**
+     * The layout map, with handness as key
+     */
+    layouts: IMotionControllerLayoutMap;
+}
+
+/**
+ * A helper-interface for the 3 meshes needed for controller button animation
+ * The meshes are provided to the _lerpButtonTransform function to calculate the current position of the value mesh
+ */
+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
+     */
+    pressedMesh: AbstractMesh;
+    /**
+     * the mesh that defines the unpressed value mesh position.
+     * This is used to find the min (or initial) position of this button
+     */
+    unpressedMesh: AbstractMesh;
+}
+
+/**
+ * A helper-interface for the 3 meshes needed for controller axis animation.
+ * This will be expanded when touchpad animations are fully supported
+ * The meshes are provided to the _lerpAxisTransform function to calculate the current position of the value mesh
+ */
+export interface IMotionControllerAxisMeshMap {
+    /**
+     * The mesh that will be changed when axis value changes
+     */
+    valueMesh: AbstractMesh;
+    /**
+     * the mesh that defines the minimum value mesh position.
+     */
+    minMesh: AbstractMesh;
+    /**
+     * the mesh that defines the maximum value mesh position.
+     */
+    maxMesh: AbstractMesh;
+}
+
+/**
+ * The elements needed for change-detection of the gamepad objects in motion controllers
+ */
+export interface IMinimalMotionControllerObject {
+    /**
+     * An array of available buttons
+     */
+    buttons: Array<{
+        /**
+        * Value of the button/trigger
+        */
+        value: number;
+        /**
+         * If the button/trigger is currently touched
+         */
+        touched: boolean;
+        /**
+         * If the button/trigger is currently pressed
+         */
+        pressed: boolean;
+    }>;
+    /**
+     * Available axes of this controller
+     */
+    axes: number[];
+}
+
+/**
+ * An Abstract Motion controller
+ * This class receives an xrInput and a profile layout and uses those to initialize the components
+ * Each component has an observable to check for changes in value and state
+ */
+export abstract class WebXRAbstractMotionController implements IDisposable {
+
+    /**
+     * Component type map
+     */
+    public static ComponentType = {
+        TRIGGER: "trigger",
+        SQUEEZE: "squeeze",
+        TOUCHPAD: "touchpad",
+        THUMBSTICK: "thumbstick",
+        BUTTON: "button"
+    };
+
+    /**
+     * The profile id of this motion controller
+     */
+    public abstract profileId: string;
+
+    /**
+     * A map of components (WebXRControllerComponent) in this motion controller
+     * Components have a ComponentType and can also have both button and axis definitions
+     */
+    public readonly components: {
+        [id: string]: WebXRControllerComponent
+    } = {};
+
+    /**
+     * 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
+     */
+    public rootMesh: Nullable<AbstractMesh>;
+
+    private _modelReady: boolean = false;
+
+    /**
+     * constructs a new abstract motion controller
+     * @param scene the scene to which the model of the controller will be added
+     * @param layout The profile layout to load
+     * @param gamepadObject The gamepad object correlating to this controller
+     * @param handness handness (left/right/none) of this controller
+     * @param _doNotLoadControllerMesh set this flag to ignore the mesh loading
+     */
+    constructor(protected scene: Scene, protected layout: IMotionControllerLayout,
+        /**
+         * The gamepad object correlating to this controller
+         */
+        public gamepadObject: IMinimalMotionControllerObject,
+        /**
+         * handness (left/right/none) of this controller
+         */
+        public handness: MotionControllerHandness,
+        _doNotLoadControllerMesh: boolean = false) {
+        // initialize the components
+        if (layout.gamepad) {
+            layout.gamepad.buttons.forEach(this._initComponent);
+        }
+        // Model is loaded in WebXRInput
+    }
+
+    private _initComponent = (id: string | null) => {
+        if (!this.layout.gamepad || !id) { return; }
+        const type = this.layout.components[id].type;
+        const buttonIndex = this.layout.gamepad.buttons.indexOf(id);
+        // search for axes
+        let axes: number[] = [];
+        this.layout.gamepad.axes.forEach((axis, index) => {
+            if (axis && axis.componentId === id) {
+                if (axis.axis === "x-axis") {
+                    axes[0] = index;
+                } else {
+                    axes[1] = index;
+                }
+            }
+        });
+        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.getComponentTypes().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
+     */
+    public getComponentTypes(): string[] {
+        return Object.keys(this.components);
+    }
+
+    /**
+     * Get the main (Select) component of this controller as defined in the layout
+     * @returns the main component of this controller
+     */
+    public getMainComponent(): WebXRControllerComponent {
+        return this.getComponent(this.layout.selectComponentId);
+    }
+
+    /**
+     * get a component based an its component id as defined in layout.components
+     * @param id the id of the component
+     * @returns the component correlates to the id or undefined if not found
+     */
+    public getComponent(id: string): WebXRControllerComponent {
+        return this.components[id];
+    }
+
+    /**
+     * Loads the model correlating to this controller
+     * When the mesh is loaded, the onModelLoadedObservable will be triggered
+     * @returns A promise fulfilled with the result of the model loading
+     */
+    public async loadModel(): Promise<boolean> {
+        let useGeneric = !this._getModelLoadingConstraints();
+        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");
+        } else {
+            loadingParams = this._getFilenameAndPath();
+        }
+        return new Promise((resolve, reject) => {
+            SceneLoader.ImportMesh("", loadingParams.path, loadingParams.filename, this.scene, (meshes: AbstractMesh[]) => {
+                if (useGeneric) {
+                    this._getGenericParentMesh(meshes);
+                } else {
+                    this._setRootMesh(meshes);
+                }
+                this._processLoadedModel(meshes);
+                this._modelReady = true;
+                this.onModelLoadedObservable.notifyObservers(this);
+                resolve(true);
+            }, null, (_scene: Scene, message: string) => {
+                Logger.Log(message);
+                Logger.Warn(`Failed to retrieve controller model of type ${this.profileId} from the remote server: ${loadingParams.path}${loadingParams.filename}`);
+                reject(message);
+            });
+        });
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Moves the axis on the controller mesh based on its current state
+     * @param axis the index of the axis
+     * @param axisValue the value of the axis which determines the meshes new position
+     * @hidden
+     */
+    protected _lerpAxisTransform(axisMap: IMotionControllerAxisMeshMap, axisValue: number): void {
+
+        if (!axisMap.minMesh.rotationQuaternion || !axisMap.maxMesh.rotationQuaternion || !axisMap.valueMesh.rotationQuaternion) {
+            return;
+        }
+
+        // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
+        let lerpValue = axisValue * 0.5 + 0.5;
+        Quaternion.SlerpToRef(
+            axisMap.minMesh.rotationQuaternion,
+            axisMap.maxMesh.rotationQuaternion,
+            lerpValue,
+            axisMap.valueMesh.rotationQuaternion);
+        Vector3.LerpToRef(
+            axisMap.minMesh.position,
+            axisMap.maxMesh.position,
+            lerpValue,
+            axisMap.valueMesh.position);
+    }
+
+    /**
+     * Moves the buttons on the controller mesh based on their current state
+     * @param buttonName the name of the button to move
+     * @param buttonValue the value of the button which determines the buttons new position
+     */
+    protected _lerpButtonTransform(buttonMap: IMotionControllerButtonMeshMap, buttonValue: number): void {
+
+        if (!buttonMap
+            || !buttonMap.unpressedMesh.rotationQuaternion
+            || !buttonMap.pressedMesh.rotationQuaternion
+            || !buttonMap.valueMesh.rotationQuaternion) {
+            return;
+        }
+
+        Quaternion.SlerpToRef(
+            buttonMap.unpressedMesh.rotationQuaternion,
+            buttonMap.pressedMesh.rotationQuaternion,
+            buttonValue,
+            buttonMap.valueMesh.rotationQuaternion);
+        Vector3.LerpToRef(
+            buttonMap.unpressedMesh.position,
+            buttonMap.pressedMesh.position,
+            buttonValue,
+            buttonMap.valueMesh.position);
+    }
+
+    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);
+    }
+
+    /**
+     * Get the filename and path for this controller's model
+     * @returns a map of filename and path
+     */
+    protected abstract _getFilenameAndPath(): { filename: string, path: string };
+    /**
+     * 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
+     */
+    protected abstract _processLoadedModel(meshes: AbstractMesh[]): void;
+    /**
+     * Set the root mesh for this controller. Important for the WebXR controller class
+     * @param meshes the loaded meshes
+     */
+    protected abstract _setRootMesh(meshes: AbstractMesh[]): void;
+    /**
+     * A function executed each frame that updates the mesh (if needed)
+     * @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.getComponentTypes().forEach((id) => this.getComponent(id).dispose());
+        if (this.rootMesh) {
+            this.rootMesh.dispose();
+        }
+    }
+}

+ 241 - 0
src/Cameras/XR/motionController/webXRControllerComponent.ts

@@ -0,0 +1,241 @@
+import { IMinimalMotionControllerObject, MotionControllerComponentType } from "./webXRAbstractController";
+import { Observable } from '../../../Misc/observable';
+import { IDisposable } from '../../../scene';
+
+/**
+ * X-Y values for axes in WebXR
+ */
+export interface IWebXRMotionControllerAxesValue {
+    /**
+     * The value of the x axis
+     */
+    x: number;
+    /**
+     * The value of the y-axis
+     */
+    y: number;
+}
+
+/**
+ * changed / previous values for the values of this component
+ */
+export interface IWebXRMotionControllerComponentChangesValues<T> {
+    /**
+     * current (this frame) value
+     */
+    current: T;
+    /**
+     * previous (last change) value
+     */
+    previous: T;
+}
+
+/**
+ * Represents changes in the component between current frame and last values recorded
+ */
+export interface IWebXRMotionControllerComponentChanges {
+    /**
+     * will be populated with previous and current values if touched changed
+     */
+    touched?: IWebXRMotionControllerComponentChangesValues<boolean>;
+    /**
+     * 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
+     */
+    value?: IWebXRMotionControllerComponentChangesValues<number>;
+    /**
+     * will be populated with previous and current values if axes changed
+     */
+    axes?: IWebXRMotionControllerComponentChangesValues<IWebXRMotionControllerAxesValue>;
+}
+/**
+ * This class represents a single component (for example button or thumbstick) of a motion controller
+ */
+export class WebXRControllerComponent implements IDisposable {
+
+    /**
+     * Observers registered here will be triggered when the state of a button changes
+     * State change is either pressed / touched / value
+     */
+    public onButtonStateChanged: 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 onAxisValueChanged: 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 = {};
+
+    /**
+     * Creates a new component for a motion controller.
+     * It is created by the motion controller itself
+     *
+     * @param id the id of this component
+     * @param type the type of the component
+     * @param _buttonIndex index in the buttons array of the gamepad
+     * @param _axesIndices indices of the values in the axes array of the gamepad
+     */
+    constructor(
+        /**
+         * the id of this component
+         */
+        public id: string,
+        /**
+         * the type of the component
+         */
+        public type: MotionControllerComponentType,
+        private _buttonIndex: number = -1,
+        private _axesIndices: number[] = []) {
+
+    }
+
+    /**
+     * Get the current value of this component
+     */
+    public get value(): number {
+        return this._currentValue;
+    }
+
+    /**
+     * is the button currently pressed
+     */
+    public get pressed(): boolean {
+        return this._pressed;
+    }
+
+    /**
+     * is the button currently touched
+     */
+    public get touched(): boolean {
+        return this._touched;
+    }
+
+    /**
+     * 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
+     */
+    public get changes(): IWebXRMotionControllerComponentChanges {
+        return this._changes;
+    }
+
+    /**
+     * Is this component a button (hence - pressable)
+     * @returns true if can be pressed
+     */
+    public isButton(): boolean {
+        return this._buttonIndex !== -1;
+    }
+
+    /**
+     * Are there axes correlating to this component
+     * @return true is axes data is available
+     */
+    public isAxes(): boolean {
+        return this._axesIndices.length !== 0;
+    }
+
+    /**
+     * update this component using the gamepad object it is in. Called on every frame
+     * @param nativeController the native gamepad controller object
+     */
+    public update(nativeController: IMinimalMotionControllerObject) {
+        let buttonUpdated = false;
+        let axesUpdate = false;
+        this._changes = {};
+
+        if (this.isButton()) {
+            const button = nativeController.buttons[this._buttonIndex];
+            if (this._currentValue !== button.value) {
+                this.changes.value = {
+                    current: button.value,
+                    previous: this._currentValue
+                };
+                buttonUpdated = true;
+                this._currentValue = button.value;
+            }
+            if (this._touched !== button.touched) {
+                this.changes.touched = {
+                    current: button.touched,
+                    previous: this._touched
+                };
+                buttonUpdated = true;
+                this._touched = button.touched;
+            }
+            if (this._pressed !== button.pressed) {
+                this.changes.pressed = {
+                    current: button.pressed,
+                    previous: this._pressed
+                };
+                buttonUpdated = true;
+                this._pressed = button.pressed;
+            }
+        }
+
+        if (this.isAxes()) {
+            if (this._axes.x !== nativeController.axes[this._axesIndices[0]]) {
+                this.changes.axes = {
+                    current: {
+                        x: nativeController.axes[this._axesIndices[0]],
+                        y: this._axes.y
+                    },
+                    previous: {
+                        x: this._axes.x,
+                        y: this._axes.y
+                    }
+                };
+                this._axes.x = nativeController.axes[this._axesIndices[0]];
+                axesUpdate = true;
+            }
+
+            if (this._axes.y !== nativeController.axes[this._axesIndices[1]]) {
+                if (this.changes.axes) {
+                    this.changes.axes.current.y = nativeController.axes[this._axesIndices[1]];
+                } else {
+                    this.changes.axes = {
+                        current: {
+                            x: this._axes.x,
+                            y: nativeController.axes[this._axesIndices[1]]
+                        },
+                        previous: {
+                            x: this._axes.x,
+                            y: this._axes.y
+                        }
+                    };
+                }
+                this._axes.y = nativeController.axes[this._axesIndices[1]];
+                axesUpdate = true;
+            }
+        }
+
+        if (buttonUpdated) {
+            this.onButtonStateChanged.notifyObservers(this);
+        }
+        if (axesUpdate) {
+            this.onAxisValueChanged.notifyObservers(this._axes);
+        }
+    }
+
+    /**
+     * Dispose this component
+     */
+    public dispose(): void {
+        this.onAxisValueChanged.clear();
+        this.onButtonStateChanged.clear();
+    }
+}

+ 80 - 0
src/Cameras/XR/motionController/webXRGenericMotionController.ts

@@ -0,0 +1,80 @@
+import {
+    WebXRAbstractMotionController,
+    IMinimalMotionControllerObject,
+    MotionControllerHandness,
+    IMotionControllerLayoutMap
+} from "./webXRAbstractController";
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { Scene } from '../../../scene';
+import { Mesh } from '../../../Meshes/mesh';
+import { Quaternion } from '../../../Maths/math.vector';
+
+// https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/generic/generic-trigger-touchpad-thumbstick.json
+const GenericTriggerLayout: IMotionControllerLayoutMap = {
+    "left-right-none": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" }
+        },
+        "gamepad": {
+            "mapping": "xr-standard",
+            "buttons": [
+                "xr-standard-trigger"
+            ],
+            "axes": []
+        }
+    }
+
+};
+
+// TODO support all generic models with xr-standard mapping at:
+// https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/registry/profiles/generic
+
+/**
+ * A generic trigger-only motion controller for WebXR
+ */
+export class WebXRGenericTriggerMotionController extends WebXRAbstractMotionController {
+    /**
+     * Static version of the profile id of this controller
+     */
+    public static ProfileId = "generic-trigger";
+
+    public profileId = WebXRGenericTriggerMotionController.ProfileId;
+
+    constructor(scene: Scene, gamepadObject: IMinimalMotionControllerObject, handness: MotionControllerHandness) {
+        super(scene, GenericTriggerLayout["left-right-none"], 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",
+            path: "https://controllers.babylonjs.com/generic/"
+        };
+    }
+
+    protected _setRootMesh(meshes: AbstractMesh[]): void {
+        this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
+
+        meshes.forEach((mesh) => {
+            mesh.isPickable = false;
+            if (!mesh.parent) {
+                mesh.setParent(this.rootMesh);
+            }
+        });
+
+        this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+    }
+
+    protected _getModelLoadingConstraints(): boolean {
+        return true;
+    }
+
+}

+ 154 - 0
src/Cameras/XR/motionController/webXRHTCViveMotionController.ts

@@ -0,0 +1,154 @@
+import {
+    IMotionControllerLayoutMap,
+    IMinimalMotionControllerObject,
+    MotionControllerHandness,
+    WebXRAbstractMotionController
+} from "./webXRAbstractController";
+import { Scene } from '../../../scene';
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { Mesh } from '../../../Meshes/mesh';
+import { Quaternion } from '../../../Maths/math.vector';
+import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
+
+const HTCViveLayout: IMotionControllerLayoutMap = {
+    "left-right-none": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-touchpad": { "type": "touchpad" },
+            "menu": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "xr-standard",
+            "buttons": [
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                "xr-standard-touchpad",
+                null,
+                "menu"
+            ],
+            "axes": [
+                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
+                { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
+            ]
+        }
+    }
+};
+
+const HTCViveLegacyLayout: IMotionControllerLayoutMap = {
+    "left-right-none": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-touchpad": { "type": "touchpad" },
+            "menu": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "",
+            "buttons": [
+                "xr-standard-touchpad",
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                "menu"
+            ],
+            "axes": [
+                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
+                { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
+            ]
+        }
+    }
+};
+
+/**
+ * The motion controller class for the standard HTC-Vive controllers
+ */
+export class WebXRHTCViveMotionController extends WebXRAbstractMotionController {
+    /**
+     * The base url used to load the left and right controller models
+     */
+    public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/vive/';
+    /**
+     * File name for the controller model.
+     */
+    public static MODEL_FILENAME: string = 'wand.babylon';
+
+    public profileId = "htc-vive";
+
+    private _modelRootNode: AbstractMesh;
+
+    constructor(scene: Scene,
+        gamepadObject: IMinimalMotionControllerObject,
+        handness: MotionControllerHandness,
+        legacyMapping: boolean = false) {
+        super(scene, legacyMapping ? HTCViveLegacyLayout["left-right-none"] : HTCViveLayout["left-right-none"], gamepadObject, handness);
+    }
+
+    protected _processLoadedModel(_meshes: AbstractMesh[]): void {
+        this.layout.gamepad!.buttons.forEach((buttonName) => {
+            const comp = buttonName && this.getComponent(buttonName);
+            if (comp) {
+                comp.onButtonStateChanged.add((component) => {
+
+                    if (!this.rootMesh) { return; }
+
+                    switch (buttonName) {
+                        case "xr-standard-trigger":
+                            (<AbstractMesh>(this._modelRootNode.getChildren()[6])).rotation.x = -component.value * 0.15;
+                            return;
+                        case "xr-standard-touchpad":
+                            return;
+                        case "xr-standard-squeeze":
+                            return;
+                        case "menu":
+                            if (component.pressed) {
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = -0.001;
+                            }
+                            else {
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = 0;
+                            }
+                            return;
+                    }
+                }, undefined, true);
+            }
+        });
+    }
+
+    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);
+
+        meshes.forEach((mesh) => { mesh.isPickable = false; });
+        this._modelRootNode = meshes[1];
+        this._modelRootNode.parent = this.rootMesh;
+        this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+    }
+
+}
+
+// register the profile
+WebXRMotionControllerManager.RegisterController("htc-vive", (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXRHTCViveMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
+});
+
+WebXRMotionControllerManager.RegisterController("htc-vive-legacy", (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXRHTCViveMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
+});

+ 264 - 0
src/Cameras/XR/motionController/webXRMicrosoftMixedRealityController.ts

@@ -0,0 +1,264 @@
+import {
+    WebXRAbstractMotionController,
+    IMinimalMotionControllerObject,
+    MotionControllerHandness,
+    IMotionControllerLayoutMap
+} from "./webXRAbstractController";
+import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { Scene } from '../../../scene';
+import { Logger } from '../../../Misc/logger';
+import { Mesh } from '../../../Meshes/mesh';
+import { Quaternion } from '../../../Maths/math.vector';
+import { SceneLoader } from '../../../Loading/sceneLoader';
+
+// https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/microsoft/microsoft-mixed-reality.json
+const MixedRealityProfile: IMotionControllerLayoutMap = {
+    "left-right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-touchpad": { "type": "touchpad" },
+            "xr-standard-thumbstick": { "type": "thumbstick" },
+            "menu": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "xr-standard",
+            "buttons": [
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                "xr-standard-touchpad",
+                "xr-standard-thumbstick",
+                "menu"
+            ],
+            "axes": [
+                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
+                { "componentId": "xr-standard-touchpad", "axis": "y-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
+            ]
+        }
+    }
+};
+
+/**
+ * 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: {
+            "valueNodeName": "VALUE",
+            "unpressedNodeName": "UNPRESSED",
+            "pressedNodeName": "PRESSED"
+        },
+        defaultAxis: {
+            "valueNodeName": "VALUE",
+            "minNodeName": "MIN",
+            "maxNodeName": "MAX"
+        },
+        buttons: {
+            "xr-standard-trigger": {
+                "rootNodeName": "SELECT",
+                "componentProperty": "button",
+                "states": ["default", "touched", "pressed"]
+            },
+            "xr-standard-squeeze": {
+                "rootNodeName": "GRASP",
+                "componentProperty": "state",
+                "states": ["pressed"]
+            },
+            "xr-standard-touchpad": {
+                "rootNodeName": "TOUCHPAD_PRESS",
+                "labelAnchorNodeName": "squeeze-label",
+                "touchPointNodeName": "TOUCH" // TODO - use this for visual feedback
+            },
+            "xr-standard-thumbstick": {
+                "rootNodeName": "THUMBSTICK_PRESS",
+                "componentProperty": "state",
+                "states": ["pressed"],
+            },
+            "menu": {
+                "rootNodeName": "MENU",
+                "componentProperty": "state",
+                "states": ["pressed"]
+            }
+        },
+        axes: {
+            "xr-standard-touchpad": {
+                "x-axis": {
+                    "rootNodeName": "TOUCHPAD_TOUCH_X"
+                },
+                "y-axis": {
+                    "rootNodeName": "TOUCHPAD_TOUCH_Y"
+                }
+            },
+            "xr-standard-thumbstick": {
+                "x-axis": {
+                    "rootNodeName": "THUMBSTICK_X"
+                },
+                "y-axis": {
+                    "rootNodeName": "THUMBSTICK_Y"
+                }
+            }
+        }
+    };
+
+    constructor(scene: Scene, gamepadObject: IMinimalMotionControllerObject, handness: MotionControllerHandness) {
+        super(scene, MixedRealityProfile["left-right"], gamepadObject, handness);
+    }
+
+    protected _processLoadedModel(_meshes: AbstractMesh[]): void {
+        if (!this.rootMesh) { return; }
+
+        // Button Meshes
+        for (let i = 0; i < this.layout.gamepad!.buttons.length; i++) {
+            const buttonName = this.layout.gamepad!.buttons[i];
+            if (buttonName) {
+                const buttonMap = (<any>this._mapping.buttons)[buttonName];
+                const buttonMeshName = buttonMap.rootNodeName;
+                if (!buttonMeshName) {
+                    Logger.Log('Skipping unknown button at index: ' + i + ' with mapped name: ' + buttonName);
+                    continue;
+                }
+
+                var buttonMesh = this._getChildByName(this.rootMesh, buttonMeshName);
+                if (!buttonMesh) {
+                    Logger.Warn('Missing button mesh with name: ' + buttonMeshName);
+                    continue;
+                }
+
+                buttonMap.valueMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.valueNodeName);
+                buttonMap.pressedMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.pressedNodeName);
+                buttonMap.unpressedMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.unpressedNodeName);
+
+                if (buttonMap.valueMesh && buttonMap.pressedMesh && buttonMap.unpressedMesh) {
+                    const comp = this.getComponent(buttonName);
+                    if (comp) {
+                        comp.onButtonStateChanged.add((component) => {
+                            this._lerpButtonTransform(buttonMap, component.value);
+                        }, undefined, true);
+                    }
+                } else {
+                    // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
+                    Logger.Warn('Missing button submesh under mesh with name: ' + buttonMeshName);
+                }
+            }
+
+        }
+
+        // Axis Meshes
+        for (let i = 0; i < this.layout.gamepad!.axes.length; ++i) {
+            const axisData = this.layout.gamepad!.axes[i];
+            if (!axisData) {
+                Logger.Log('Skipping unknown axis at index: ' + i);
+                continue;
+            }
+
+            const axisMap = (<any>this._mapping.axes)[axisData.componentId][axisData.axis];
+
+            var axisMesh = this._getChildByName(this.rootMesh, axisMap.rootNodeName);
+            if (!axisMesh) {
+                Logger.Warn('Missing axis mesh with name: ' + axisMap.rootNodeName);
+                continue;
+            }
+
+            axisMap.valueMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.valueNodeName);
+            axisMap.minMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.minNodeName);
+            axisMap.maxMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.maxNodeName);
+
+            if (axisMap.valueMesh && axisMap.minMesh && axisMap.maxMesh) {
+                const comp = this.getComponent(axisData.componentId);
+                if (comp) {
+                    comp.onAxisValueChanged.add((axisValues) => {
+                        const value = axisData.axis === "x-axis" ? axisValues.x : axisValues.y;
+                        this._lerpAxisTransform(axisMap, value);
+                    }, undefined, true);
+                }
+
+            } else {
+                // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
+                Logger.Warn('Missing axis submesh under mesh with name: ' + axisMap.rootNodeName);
+            }
+        }
+    }
+
+    // Look through all children recursively. This will return null if no mesh exists with the given name.
+    private _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.
+    private _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh {
+        return <AbstractMesh>node.getChildren((n) => n.name == name, true)[0];
+    }
+
+    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;
+        let rootMesh;
+        // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
+        for (let i = 0; i < meshes.length; i++) {
+            let mesh = meshes[i];
+
+            mesh.isPickable = false;
+
+            if (!mesh.parent) {
+                // Handle root node, attach to the new parentMesh
+                rootMesh = mesh;
+            }
+        }
+
+        if (rootMesh) {
+            rootMesh.setParent(this.rootMesh);
+        }
+
+        this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+    }
+
+}
+
+// register the profile
+WebXRMotionControllerManager.RegisterController("microsoft-mixed-reality", (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXRMicrosoftMixedRealityController(scene, <any>(xrInput.gamepad), xrInput.handedness);
+});

+ 131 - 0
src/Cameras/XR/motionController/webXRMotionControllerManager.ts

@@ -0,0 +1,131 @@
+import {
+    WebXRAbstractMotionController,
+} from './webXRAbstractController';
+import { WebXRGenericTriggerMotionController } from './webXRGenericMotionController';
+import { Scene } from '../../../scene';
+
+/**
+ * A construction function type to create a new controller based on an xrInput object
+ */
+export type MotionControllerConstructor = (xrInput: XRInputSource, scene: Scene) => WebXRAbstractMotionController;
+
+/**
+ * The MotionController Manager manages all registered motion controllers and loads the right one when needed.
+ *
+ * When this repository is complete: https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/assets
+ * it should be replaced with auto-loaded controllers.
+ *
+ * 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[] } = {};
+
+    /**
+     * 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;
+    }
+
+    /**
+     * When acquiring a new xrInput object (usually by the WebXRInput class), match it with the correct profile.
+     * The order of search:
+     *
+     * 1) Iterate the profiles array of the xr input and try finding a corresponding motion controller
+     * 2) (If not found) search in the gamepad id and try using it (legacy versions only)
+     * 3) search for registered fallbacks (should be redundant, nonetheless it makes sense to check)
+     * 4) return the generic trigger controller if none were found
+     *
+     * @param xrInput the xrInput to which a new controller is initialized
+     * @param scene the scene to which the model will be added
+     * @return the motion controller class for this profile id or the generic standard class if none was found
+     */
+    public static GetMotionControllerWithXRInput(xrInput: XRInputSource, scene: Scene): WebXRAbstractMotionController {
+        for (let i = 0; i < xrInput.profiles.length; ++i) {
+            const constructionFunction = this._AvailableControllers[xrInput.profiles[i]];
+            if (constructionFunction) {
+                return constructionFunction(xrInput, scene);
+            }
+        }
+        // try using the gamepad id
+        if (xrInput.gamepad && xrInput.gamepad.id) {
+            switch (xrInput.gamepad.id) {
+                case (xrInput.gamepad.id.match(/oculus touch/gi) ? xrInput.gamepad.id : undefined):
+                    // oculus in gamepad id - legacy mapping
+                    return this._AvailableControllers["oculus-touch-legacy"](xrInput, scene);
+                case (xrInput.gamepad.id.match(/Spatial Controller/gi) ? xrInput.gamepad.id : undefined):
+                    // oculus in gamepad id - legacy mapping
+                    return this._AvailableControllers["microsoft-mixed-reality"](xrInput, scene);
+                case (xrInput.gamepad.id.match(/openvr/gi) ? xrInput.gamepad.id : undefined):
+                    // oculus in gamepad id - legacy mapping
+                    return this._AvailableControllers["htc-vive-legacy"](xrInput, scene);
+            }
+        }
+        // check fallbacks
+        for (let i = 0; i < xrInput.profiles.length; ++i) {
+            const fallbacks = this.FindFallbackWithProfileId(xrInput.profiles[i]);
+            for (let j = 0; j < fallbacks.length; ++j) {
+                const constructionFunction = this._AvailableControllers[fallbacks[j]];
+                if (constructionFunction) {
+                    return constructionFunction(xrInput, scene);
+                }
+            }
+        }
+        // return the most generic thing we have
+        return this._AvailableControllers[WebXRGenericTriggerMotionController.ProfileId](xrInput, scene);
+    }
+
+    /**
+     * 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[] {
+        return this._Fallbacks[profileId] || [];
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+    /**
+     * 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("microsoft-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", ["microsoft-mixed-reality", "generic-trigger-squeeze-touchpad-thumbstick"]);
+        this.RegisterFallbacksForProfileId("samsung-odyssey", ["generic-touchpad"]);
+        this.RegisterFallbacksForProfileId("valve-index", ["generic-trigger-squeeze-touchpad-thumbstick"]);
+    }
+}
+
+// register the generic profile(s) here so we will at least have them
+WebXRMotionControllerManager.RegisterController(WebXRGenericTriggerMotionController.ProfileId, (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXRGenericTriggerMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
+});
+
+// register fallbacks
+WebXRMotionControllerManager.DefaultFallbacks();

+ 277 - 0
src/Cameras/XR/motionController/webXROculusTouchMotionController.ts

@@ -0,0 +1,277 @@
+import {
+    WebXRAbstractMotionController,
+    IMinimalMotionControllerObject,
+    MotionControllerHandness,
+    IMotionControllerLayoutMap
+} from "./webXRAbstractController";
+import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { Scene } from '../../../scene';
+import { Mesh } from '../../../Meshes/mesh';
+import { Quaternion } from '../../../Maths/math.vector';
+
+// https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/microsoft/microsoft-mixed-reality.json
+const OculusTouchLayouts: IMotionControllerLayoutMap = {
+    "left": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-thumbstick": { "type": "thumbstick" },
+            "a-button": { "type": "button" },
+            "b-button": { "type": "button" },
+            "thumbrest": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "xr-standard",
+            "buttons": [
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                null,
+                "xr-standard-thumbstick",
+                "a-button",
+                "b-button",
+                "thumbrest"
+            ],
+            "axes": [
+                null,
+                null,
+                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
+            ]
+        }
+    },
+    "right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-thumbstick": { "type": "thumbstick" },
+            "x-button": { "type": "button" },
+            "y-button": { "type": "button" },
+            "thumbrest": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "xr-standard",
+            "buttons": [
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                null,
+                "xr-standard-thumbstick",
+                "x-button",
+                "y-button",
+                "thumbrest"
+            ],
+            "axes": [
+                null,
+                null,
+                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
+            ]
+        }
+    }
+};
+
+const OculusTouchLegacyLayouts: IMotionControllerLayoutMap = {
+    "left": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-thumbstick": { "type": "thumbstick" },
+            "a-button": { "type": "button" },
+            "b-button": { "type": "button" },
+            "thumbrest": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "",
+            "buttons": [
+                "xr-standard-thumbstick",
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                "a-button",
+                "b-button",
+                "thumbrest"
+            ],
+            "axes": [
+                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
+            ]
+        }
+    },
+    "right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": { "type": "trigger" },
+            "xr-standard-squeeze": { "type": "squeeze" },
+            "xr-standard-thumbstick": { "type": "thumbstick" },
+            "x-button": { "type": "button" },
+            "y-button": { "type": "button" },
+            "thumbrest": { "type": "button" }
+        },
+        "gamepad": {
+            "mapping": "",
+            "buttons": [
+                "xr-standard-thumbstick",
+                "xr-standard-trigger",
+                "xr-standard-squeeze",
+                "x-button",
+                "y-button",
+                "thumbrest"
+            ],
+            "axes": [
+                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
+                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
+            ]
+        }
+    }
+};
+
+/**
+ * The motion controller class for oculus touch (quest, rift).
+ * This class supports legacy mapping as well the standard xr mapping
+ */
+export class WebXROculusTouchMotionController extends WebXRAbstractMotionController {
+    /**
+     * The base url used to load the left and right controller models
+     */
+    public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculus/';
+    /**
+     * The name of the left controller model file
+     */
+    public static MODEL_LEFT_FILENAME: string = 'left.babylon';
+    /**
+     * The name of the right controller model file
+     */
+    public static MODEL_RIGHT_FILENAME: string = 'right.babylon';
+
+    /**
+     * Base Url for the Quest controller model.
+     */
+    public static QUEST_MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculusQuest/';
+
+    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);
+    }
+
+    protected _processLoadedModel(_meshes: AbstractMesh[]): void {
+
+        const isQuest = this._isQuest();
+        const triggerDirection = this.handness === 'right' ? -1 : 1;
+
+        this.layout.gamepad!.buttons.forEach((buttonName) => {
+            const comp = buttonName && this.getComponent(buttonName);
+            if (comp) {
+                comp.onButtonStateChanged.add((component) => {
+
+                    if (!this.rootMesh) { return; }
+
+                    switch (buttonName) {
+                        case "xr-standard-trigger": // index trigger
+                            if (!isQuest) {
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[3])).rotation.x = -component.value * 0.20;
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.y = -component.value * 0.005;
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.z = -component.value * 0.005;
+                            }
+                            return;
+                        case "xr-standard-squeeze":  // secondary trigger
+                            if (!isQuest) {
+                                (<AbstractMesh>(this._modelRootNode.getChildren()[4])).position.x = triggerDirection * component.value * 0.0035;
+                            }
+                            return;
+                        case "xr-standard-thumbstick": // thumbstick
+                            return;
+                        case "a-button":
+                        case "x-button":
+                            if (!isQuest) {
+                                if (component.pressed) {
+                                    (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = -0.001;
+                                }
+                                else {
+                                    (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = 0;
+                                }
+                            }
+                            return;
+                        case "b-button":
+                        case "y-button":
+                            if (!isQuest) {
+                                if (component.pressed) {
+                                    (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = -0.001;
+                                }
+                                else {
+                                    (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = 0;
+                                }
+                            }
+                            return;
+                    }
+                }, undefined, true);
+            }
+        });
+    }
+
+    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);
+
+        meshes.forEach((mesh) => { mesh.isPickable = false; });
+        if (this._isQuest()) {
+            this._modelRootNode = meshes[0];
+        } else {
+            this._modelRootNode = meshes[1];
+            this.rootMesh.position.y = 0.034;
+            this.rootMesh.position.z = 0.052;
+        }
+        this._modelRootNode.parent = this.rootMesh;
+    }
+
+}
+
+// register the profile
+WebXRMotionControllerManager.RegisterController("oculus-touch", (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
+});
+
+WebXRMotionControllerManager.RegisterController("oculus-touch-legacy", (xrInput: XRInputSource, scene: Scene) => {
+    return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
+});

+ 9 - 12
src/Cameras/XR/webXRCamera.ts

@@ -44,6 +44,15 @@ export class WebXRCamera extends FreeCamera {
             // first frame - camera's y position should be 0 for the correct offset
             this._firstFrame = true;
         });
+
+        // Check transformation changes on each frame. Callback is added to be first so that the transformation will be
+        // applied to the rest of the elements using the referenceSpace object
+        this._xrSessionManager.onXRFrameObservable.add((frame) => {
+            if (!this._firstFrame) {
+                this._updateReferenceSpace();
+            }
+            this._updateFromXRSession();
+        }, undefined, true);
     }
 
     private _updateNumberOfRigCameras(viewCount = 1) {
@@ -74,18 +83,6 @@ export class WebXRCamera extends FreeCamera {
         this.rigCameras[1].outputRenderTarget = null;
     }
 
-    /**
-     * Updates the cameras position from the current pose information of the  XR session
-     * @param xrSessionManager the session containing pose information
-     */
-    public update() {
-        if (!this._firstFrame) {
-            this._updateReferenceSpace();
-        }
-        this._updateFromXRSession();
-        super.update();
-    }
-
     private _updateReferenceSpace(): boolean {
         // were position & rotation updated OUTSIDE of the xr update loop
         if (!this.position.equals(this._referencedPosition) || !this.rotationQuaternion.equals(this._referenceQuaternion)) {

+ 14 - 16
src/Cameras/XR/webXRController.ts

@@ -1,10 +1,10 @@
-import { Nullable } from "../../types";
 import { Observable } from "../../Misc/observable";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
 import { Quaternion, Vector3 } from '../../Maths/math.vector';
 import { Ray } from '../../Culling/ray';
 import { Scene } from '../../scene';
-import { WebVRController } from '../../Gamepads/Controllers/webVRController';
+import { WebXRAbstractMotionController } from './motionController/webXRAbstractController';
+import { WebXRMotionControllerManager } from './motionController/webXRMotionControllerManager';
 /**
  * Represents an XR input
  */
@@ -24,7 +24,7 @@ export class WebXRController {
      * Using this object it is possible to get click events and trackpad changes of the
      * webxr controller that is currently being used.
      */
-    public gamepadController?: WebVRController;
+    public gamepadController?: WebXRAbstractMotionController;
 
     /**
      * Event that fires when the controller is removed/disposed
@@ -44,22 +44,22 @@ export class WebXRController {
     constructor(
         private scene: Scene,
         /** The underlying input source for the controller  */
-        public inputSource: XRInputSource,
-        private parentContainer: Nullable<AbstractMesh> = null) {
+        public inputSource: XRInputSource) {
         this.pointer = new AbstractMesh("controllerPointer", scene);
         this.pointer.rotationQuaternion = new Quaternion();
-        if (parentContainer) {
-            parentContainer.addChild(this.pointer);
-        }
 
         if (this.inputSource.gripSpace) {
             this.grip = new AbstractMesh("controllerGrip", this.scene);
             this.grip.rotationQuaternion = new Quaternion();
-            if (this.parentContainer) {
-                this.parentContainer.addChild(this.grip);
-            }
-        } else if (this.inputSource.gamepad) {
-            this._gamepadMode = true;
+        }
+
+        // for now only load motion controllers if gamepad available
+        if (this.inputSource.gamepad) {
+            this.gamepadController = WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, scene);
+            // if the model is loaded, do your thing
+            this.gamepadController.onModelLoadedObservable.addOnce(() => {
+                this.gamepadController!.rootMesh!.parent = this.pointer;
+            });
         }
     }
 
@@ -97,9 +97,7 @@ export class WebXRController {
         }
         if (this.gamepadController) {
             // either update buttons only or also position, if in gamepad mode
-            this.gamepadController.isXR = !this._gamepadMode;
-            this.gamepadController.update();
-            this.gamepadController.isXR = true;
+            this.gamepadController.updateFromXRFrame(xrFrame);
         }
     }
 

+ 0 - 77
src/Cameras/XR/webXRControllerModelLoader.ts

@@ -1,77 +0,0 @@
-import { Quaternion, Vector3 } from '../../Maths/math.vector';
-import { XRWindowsMotionController } from '../../Gamepads/Controllers/windowsMotionController';
-import { OculusTouchController } from '../../Gamepads/Controllers/oculusTouchController';
-import { WebXRInput } from './webXRInput';
-import { ViveController } from '../../Gamepads/Controllers/viveController';
-import { WebVRController } from '../../Gamepads/Controllers/webVRController';
-import { Observable } from '../../Misc/observable';
-import { WebXRController } from './webXRController';
-
-/**
- * Loads a controller model and adds it as a child of the WebXRControllers grip when the controller is created
- */
-export class WebXRControllerModelLoader {
-
-    /**
-     * an observable that triggers when a new model (the mesh itself) was initialized.
-     * To know when the mesh was loaded use the controller's own modelLoaded() method
-     */
-    public onControllerModelLoaded = new Observable<WebXRController>();
-    /**
-     * Creates the WebXRControllerModelLoader
-     * @param input xr input that creates the controllers
-     */
-    constructor(input: WebXRInput) {
-        input.onControllerAddedObservable.add((c) => {
-            if (!c.inputSource.gamepad) {
-                return;
-            }
-
-            let controllerModel: WebVRController;
-
-            let rotation: Quaternion;
-            const position = new Vector3();
-
-            switch (c.inputSource.gamepad.id) {
-                case "htc-vive": {
-                    controllerModel = new ViveController(c.inputSource.gamepad);
-                    rotation = Quaternion.FromEulerAngles(0, Math.PI, 0);
-                    break;
-                }
-                case "oculus-touch": {
-                    controllerModel = new OculusTouchController(c.inputSource.gamepad);
-                    rotation = Quaternion.FromEulerAngles(0, Math.PI, 0);
-                    position.y = 0.034;
-                    position.z = 0.052;
-                    break;
-                }
-                case "oculus-quest": {
-                    OculusTouchController._IsQuest = true;
-                    controllerModel = new OculusTouchController(c.inputSource.gamepad);
-                    rotation = Quaternion.FromEulerAngles(Math.PI / -4, Math.PI, 0);
-                    break;
-                }
-                default: {
-                    controllerModel = new XRWindowsMotionController(c.inputSource.gamepad);
-                    rotation = Quaternion.FromEulerAngles(0, Math.PI, 0);
-                    break;
-                }
-            }
-
-            controllerModel.hand = c.inputSource.handedness;
-            controllerModel.isXR = true;
-            controllerModel.initControllerMesh(c.getScene(), (m) => {
-                controllerModel.mesh!.parent = c.grip || null;
-                controllerModel.mesh!.rotationQuaternion = rotation;
-                controllerModel.mesh!.position = position;
-                m.isPickable = false;
-                m.getChildMeshes(false).forEach((m) => {
-                    m.isPickable = false;
-                });
-                this.onControllerModelLoaded.notifyObservers(c);
-            });
-
-            c.gamepadController = controllerModel;
-        });
-    }
-}

+ 6 - 6
src/Cameras/XR/webXRControllerTeleportation.ts

@@ -121,8 +121,8 @@ export class WebXRControllerTeleportation {
                             if (forwardReadyToTeleport) {
                                 // Teleport the users feet to where they targeted
                                 this._tmpVector.copyFrom(teleportationTarget.position);
-                                this._tmpVector.y += input.baseExperience.camera.position.y;
-                                input.baseExperience.camera.position.copyFrom(this._tmpVector);
+                                this._tmpVector.y += input.xrCamera.position.y;
+                                input.xrCamera.position.copyFrom(this._tmpVector);
                             }
                             forwardReadyToTeleport = false;
                         }
@@ -133,7 +133,7 @@ export class WebXRControllerTeleportation {
                         } else {
                             if (backwardReadyToTeleport) {
                                 this._tmpVector.set(0, 0, -1);
-                                this._tmpVector.rotateByQuaternionToRef(input.baseExperience.camera.rotationQuaternion, this._tmpVector);
+                                this._tmpVector.rotateByQuaternionToRef(input.xrCamera.rotationQuaternion, this._tmpVector);
                                 this._tmpVector.y = 0;
                                 this._tmpVector.normalize();
                                 this._tmpVector.y = -1.5;
@@ -146,7 +146,7 @@ export class WebXRControllerTeleportation {
                                 if (pick && pick.pickedPoint) {
                                     // Teleport the users feet to where they targeted
                                     this._tmpVector.copyFrom(pick.pickedPoint);
-                                    input.baseExperience.camera.position.addInPlace(this._tmpVector);
+                                    input.xrCamera.position.addInPlace(this._tmpVector);
                                 }
                             }
                             backwardReadyToTeleport = false;
@@ -158,7 +158,7 @@ export class WebXRControllerTeleportation {
                             leftReadyToTeleport = true;
                         } else {
                             if (leftReadyToTeleport) {
-                                input.baseExperience.camera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, -Math.PI / 4, 0));
+                                input.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, -Math.PI / 4, 0));
                             }
                             leftReadyToTeleport = false;
                         }
@@ -166,7 +166,7 @@ export class WebXRControllerTeleportation {
                             rightReadyToTeleport = true;
                         } else {
                             if (rightReadyToTeleport) {
-                                input.baseExperience.camera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, Math.PI / 4, 0));
+                                input.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, Math.PI / 4, 0));
                             }
                             rightReadyToTeleport = false;
                         }

+ 9 - 10
src/Cameras/XR/webXRDefaultExperience.ts

@@ -1,7 +1,6 @@
 import { WebXRExperienceHelper } from "./webXRExperienceHelper";
 import { Scene } from '../../scene';
-import { WebXRInput } from './webXRInput';
-import { WebXRControllerModelLoader } from './webXRControllerModelLoader';
+import { WebXRInput, IWebXRInputOptions } from './webXRInput';
 import { WebXRControllerPointerSelection } from './webXRControllerPointerSelection';
 import { WebXRControllerTeleportation } from './webXRControllerTeleportation';
 import { WebXRRenderTarget } from './webXRTypes';
@@ -16,7 +15,7 @@ export class WebXRDefaultExperienceOptions {
     /**
      * Floor meshes that should be used for teleporting
      */
-    public floorMeshes: Array<AbstractMesh>;
+    public floorMeshes?: Array<AbstractMesh>;
 
     /**
      * Enable or disable default UI to enter XR
@@ -32,6 +31,11 @@ export class WebXRDefaultExperienceOptions {
      * optional UI options. This can be used among other to change session mode and reference space type
      */
     public uiOptions?: WebXREnterExitUIOptions;
+
+    /**
+     * Disable the controller mesh-loading. Can be used if you want to load your own meshes
+     */
+    public inputOptions?: IWebXRInputOptions;
 }
 
 /**
@@ -47,10 +51,6 @@ export class WebXRDefaultExperience {
      */
     public input: WebXRInput;
     /**
-     * Loads the controller models
-     */
-    public controllerModelLoader: WebXRControllerModelLoader;
-    /**
      * Enables laser pointer and selection
      */
     public pointerSelection: WebXRControllerPointerSelection;
@@ -73,7 +73,7 @@ export class WebXRDefaultExperience {
      * @param options options for basic configuration
      * @returns resulting WebXRDefaultExperience
      */
-    public static CreateAsync(scene: Scene, options: WebXRDefaultExperienceOptions) {
+    public static CreateAsync(scene: Scene, options: WebXRDefaultExperienceOptions = {}) {
         var result = new WebXRDefaultExperience();
 
         // Create base experience
@@ -81,8 +81,7 @@ export class WebXRDefaultExperience {
             result.baseExperience = xrHelper;
 
             // Add controller support
-            result.input = new WebXRInput(xrHelper);
-            result.controllerModelLoader = new WebXRControllerModelLoader(result.input);
+            result.input = new WebXRInput(xrHelper.sessionManager, xrHelper.camera, options.inputOptions);
             result.pointerSelection = new WebXRControllerPointerSelection(result.input);
 
             if (options.floorMeshes) {

+ 39 - 27
src/Cameras/XR/webXRInput.ts

@@ -1,11 +1,20 @@
 import { Nullable } from "../../types";
 import { Observer, Observable } from "../../Misc/observable";
 import { IDisposable } from "../../scene";
-import { WebXRExperienceHelper } from "./webXRExperienceHelper";
 import { WebXRController } from './webXRController';
-import { WebXRState } from './webXRTypes';
+import { WebXRSessionManager } from './webXRSessionManager';
+import { WebXRCamera } from './webXRCamera';
 
 /**
+ * The schema for initialization options of the XR Input class
+ */
+export interface IWebXRInputOptions {
+    /**
+     * If set to true no model will be automatically loaded
+     */
+    doNotLoadControllerMeshes?: boolean;
+}
+/**
  * XR input used to track XR inputs such as controllers/rays
  */
 export class WebXRInput implements IDisposable {
@@ -14,7 +23,8 @@ export class WebXRInput implements IDisposable {
      */
     public controllers: Array<WebXRController> = [];
     private _frameObserver: Nullable<Observer<any>>;
-    private _stateObserver: Nullable<Observer<any>>;
+    private _sessionEndedObserver: Nullable<Observer<any>>;
+    private _sessionInitObserver: Nullable<Observer<any>>;
     /**
      * Event when a controller has been connected/added
      */
@@ -26,37 +36,35 @@ export class WebXRInput implements IDisposable {
 
     /**
      * Initializes the WebXRInput
-     * @param baseExperience experience helper which the input should be created for
+     * @param xrSessionManager the xr session manager for this session
+     * @param xrCamera the WebXR camera for this session. Mainly used for teleportation
+     * @param options = initialization options for this xr input
      */
     public constructor(
         /**
-         * Base experience the input listens to
+         * the xr session manager for this session
+         */
+        public xrSessionManager: WebXRSessionManager,
+        /**
+         * the WebXR camera for this session. Mainly used for teleportation
          */
-        public baseExperience: WebXRExperienceHelper
+        public xrCamera: WebXRCamera,
+        private readonly options: IWebXRInputOptions = {}
     ) {
         // Remove controllers when exiting XR
-        this._stateObserver = baseExperience.onStateChangedObservable.add((s) => {
-            if (s === WebXRState.NOT_IN_XR) {
-                this._addAndRemoveControllers([], this.controllers.map((c) => {return c.inputSource; }));
-            }
+        this._sessionEndedObserver = this.xrSessionManager.onXRSessionEnded.add(() => {
+            this._addAndRemoveControllers([], this.controllers.map((c) => { return c.inputSource; }));
         });
 
-        this._frameObserver = baseExperience.sessionManager.onXRFrameObservable.add(() => {
-            if (!baseExperience.sessionManager.currentFrame) {
-                return;
-            }
-
-            // Start listing to input add/remove event
-            if (this.controllers.length == 0 && baseExperience.sessionManager.session.inputSources && baseExperience.sessionManager.session.inputSources.length > 0) {
-                this._addAndRemoveControllers(baseExperience.sessionManager.session.inputSources, []);
-                baseExperience.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
-            }
+        this._sessionInitObserver = this.xrSessionManager.onXRSessionInit.add((session) => {
+            session.addEventListener("inputsourceschange", this._onInputSourcesChange);
+        });
 
+        this._frameObserver = this.xrSessionManager.onXRFrameObservable.add((frame) => {
             // Update controller pose info
             this.controllers.forEach((controller) => {
-                controller.updateFromXRFrame(baseExperience.sessionManager.currentFrame!, baseExperience.sessionManager.referenceSpace);
+                controller.updateFromXRFrame(frame, this.xrSessionManager.referenceSpace);
             });
-
         });
     }
 
@@ -66,11 +74,14 @@ export class WebXRInput implements IDisposable {
 
     private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
         // Add controllers if they don't already exist
-        let sources = this.controllers.map((c) => {return c.inputSource; });
+        let sources = this.controllers.map((c) => { return c.inputSource; });
         for (let input of addInputs) {
             if (sources.indexOf(input) === -1) {
-                let controller = new WebXRController(this.baseExperience.camera._scene, input);
+                let controller = new WebXRController(this.xrSessionManager.scene, input);
                 this.controllers.push(controller);
+                if (!this.options.doNotLoadControllerMeshes && controller.gamepadController) {
+                    controller.gamepadController.loadModel();
+                }
                 this.onControllerAddedObservable.notifyObservers(controller);
             }
         }
@@ -81,7 +92,7 @@ export class WebXRInput implements IDisposable {
         this.controllers.forEach((c) => {
             if (removeInputs.indexOf(c.inputSource) === -1) {
                 keepControllers.push(c);
-            }else {
+            } else {
                 removedControllers.push(c);
             }
         });
@@ -100,7 +111,8 @@ export class WebXRInput implements IDisposable {
         this.controllers.forEach((c) => {
             c.dispose();
         });
-        this.baseExperience.sessionManager.onXRFrameObservable.remove(this._frameObserver);
-        this.baseExperience.onStateChangedObservable.remove(this._stateObserver);
+        this.xrSessionManager.onXRFrameObservable.remove(this._frameObserver);
+        this.xrSessionManager.onXRSessionInit.remove(this._sessionInitObserver);
+        this.xrSessionManager.onXRSessionEnded.remove(this._sessionEndedObserver);
     }
 }

+ 2 - 2
src/Engines/thinEngine.ts

@@ -132,14 +132,14 @@ export class ThinEngine {
      */
     // Not mixed with Version for tooling purpose.
     public static get NpmPackage(): string {
-        return "babylonjs@4.1.0-beta.17";
+        return "babylonjs@4.1.0-beta.18";
     }
 
     /**
      * Returns the current version of the framework
      */
     public static get Version(): string {
-        return "4.1.0-beta.17";
+        return "4.1.0-beta.18";
     }
 
     /**

+ 1 - 1
src/Lights/directionalLight.ts

@@ -218,7 +218,7 @@ export class DirectionalLight extends ShadowLight {
     protected _buildUniformLayout(): void {
         this._uniformBuffer.addUniform("vLightData", 4);
         this._uniformBuffer.addUniform("vLightDiffuse", 4);
-        this._uniformBuffer.addUniform("vLightSpecular", 3);
+        this._uniformBuffer.addUniform("vLightSpecular", 4);
         this._uniformBuffer.addUniform("shadowsInfo", 3);
         this._uniformBuffer.addUniform("depthValues", 2);
         this._uniformBuffer.create();

+ 1 - 1
src/Lights/hemisphericLight.ts

@@ -48,7 +48,7 @@ export class HemisphericLight extends Light {
     protected _buildUniformLayout(): void {
         this._uniformBuffer.addUniform("vLightData", 4);
         this._uniformBuffer.addUniform("vLightDiffuse", 4);
-        this._uniformBuffer.addUniform("vLightSpecular", 3);
+        this._uniformBuffer.addUniform("vLightSpecular", 4);
         this._uniformBuffer.addUniform("vLightGround", 3);
         this._uniformBuffer.addUniform("shadowsInfo", 3);
         this._uniformBuffer.addUniform("depthValues", 2);

+ 3 - 4
src/Lights/light.ts

@@ -381,10 +381,9 @@ export abstract class Light extends Node {
      * @param scene The scene where the light belongs to
      * @param effect The effect we are binding the data to
      * @param useSpecular Defines if specular is supported
-     * @param usePhysicalLightFalloff Specifies whether the light falloff is defined physically or not
      * @param rebuildInParallel Specifies whether the shader is rebuilding in parallel
      */
-    public bindLight(lightIndex: number, scene: Scene, effect: Effect, useSpecular: boolean, usePhysicalLightFalloff = false, rebuildInParallel = false): void {
+    public _bindLight(lightIndex: number, scene: Scene, effect: Effect, useSpecular: boolean, rebuildInParallel = false): void {
         let iAsString = lightIndex.toString();
         let needUpdate = false;
 
@@ -402,10 +401,10 @@ export abstract class Light extends Node {
             this.transferToEffect(effect, iAsString);
 
             this.diffuse.scaleToRef(scaledIntensity, TmpColors.Color3[0]);
-            this._uniformBuffer.updateColor4("vLightDiffuse", TmpColors.Color3[0], usePhysicalLightFalloff ? this.radius : this.range, iAsString);
+            this._uniformBuffer.updateColor4("vLightDiffuse", TmpColors.Color3[0], this.range, iAsString);
             if (useSpecular) {
                 this.specular.scaleToRef(scaledIntensity, TmpColors.Color3[1]);
-                this._uniformBuffer.updateColor3("vLightSpecular", TmpColors.Color3[1], iAsString);
+                this._uniformBuffer.updateColor4("vLightSpecular", TmpColors.Color3[1], this.radius, iAsString);
             }
             needUpdate = true;
         }

+ 1 - 1
src/Lights/pointLight.ts

@@ -153,7 +153,7 @@ export class PointLight extends ShadowLight {
     protected _buildUniformLayout(): void {
         this._uniformBuffer.addUniform("vLightData", 4);
         this._uniformBuffer.addUniform("vLightDiffuse", 4);
-        this._uniformBuffer.addUniform("vLightSpecular", 3);
+        this._uniformBuffer.addUniform("vLightSpecular", 4);
         this._uniformBuffer.addUniform("vLightFalloff", 4);
         this._uniformBuffer.addUniform("shadowsInfo", 3);
         this._uniformBuffer.addUniform("depthValues", 2);

+ 1 - 1
src/Lights/spotLight.ts

@@ -309,7 +309,7 @@ export class SpotLight extends ShadowLight {
     protected _buildUniformLayout(): void {
         this._uniformBuffer.addUniform("vLightData", 4);
         this._uniformBuffer.addUniform("vLightDiffuse", 4);
-        this._uniformBuffer.addUniform("vLightSpecular", 3);
+        this._uniformBuffer.addUniform("vLightSpecular", 4);
         this._uniformBuffer.addUniform("vLightDirection", 3);
         this._uniformBuffer.addUniform("vLightFalloff", 4);
         this._uniformBuffer.addUniform("shadowsInfo", 3);

+ 2 - 2
src/Materials/Node/Blocks/Dual/lightBlock.ts

@@ -189,9 +189,9 @@ export class LightBlock extends NodeMaterialBlock {
         const scene = mesh.getScene();
 
         if (!this.light) {
-            MaterialHelper.BindLights(scene, mesh, effect, true, nodeMaterial.maxSimultaneousLights, false);
+            MaterialHelper.BindLights(scene, mesh, effect, true, nodeMaterial.maxSimultaneousLights);
         } else {
-            MaterialHelper.BindLight(this.light, this._lightId, scene, effect, true, false);
+            MaterialHelper.BindLight(this.light, this._lightId, scene, effect, true);
         }
     }
 

+ 12 - 10
src/Materials/Node/Blocks/Input/inputBlock.ts

@@ -541,22 +541,24 @@ export class InputBlock extends NodeMaterialBlock {
     }
 
     protected _dumpPropertiesCode() {
+        let variableName = this._codeVariableName;
+
         if (this.isAttribute) {
-            return `${this._codeVariableName}.setAsAttribute("${this.name}");\r\n`;
+            return `${variableName}.setAsAttribute("${this.name}");\r\n`;
         }
         if (this.isSystemValue) {
-            return `${this._codeVariableName}.setAsSystemValue(BABYLON.NodeMaterialSystemValues.${NodeMaterialSystemValues[this._systemValue!]});\r\n`;
+            return `${variableName}.setAsSystemValue(BABYLON.NodeMaterialSystemValues.${NodeMaterialSystemValues[this._systemValue!]});\r\n`;
         }
         if (this.isUniform) {
             let valueString = "";
             switch (this.type) {
                 case NodeMaterialBlockConnectionPointTypes.Float:
-                    let returnValue = `${this._codeVariableName}.value = ${this.value};\r\n`;
+                    let returnValue = `${variableName}.value = ${this.value};\r\n`;
 
-                    returnValue += `${this._codeVariableName}.min = ${this.min};\r\n`;
-                    returnValue += `${this._codeVariableName}.max = ${this.max};\r\n`;
-                    returnValue += `${this._codeVariableName}.matrixMode = ${this.matrixMode};\r\n`;
-                    returnValue += `${this._codeVariableName}.animationType  = BABYLON.AnimatedInputBlockTypes.${AnimatedInputBlockTypes[this.animationType]};\r\n`;
+                    returnValue += `${variableName}.min = ${this.min};\r\n`;
+                    returnValue += `${variableName}.max = ${this.max};\r\n`;
+                    returnValue += `${variableName}.matrixMode = ${this.matrixMode};\r\n`;
+                    returnValue += `${variableName}.animationType  = BABYLON.AnimatedInputBlockTypes.${AnimatedInputBlockTypes[this.animationType]};\r\n`;
 
                     return returnValue;
                 case NodeMaterialBlockConnectionPointTypes.Vector2:
@@ -575,9 +577,9 @@ export class InputBlock extends NodeMaterialBlock {
                     valueString = `new BABYLON.Color4(${this.value.r}, ${this.value.g}, ${this.value.b}, ${this.value.a})`;
                     break;
             }
-            let finalOutput = `${this._codeVariableName}.value = ${valueString};\r\n`;
-            finalOutput += `${this._codeVariableName}.isConstant = ${this.isConstant ? "true" : "false"};\r\n`;
-            finalOutput += `${this._codeVariableName}.visibleInInspector = ${this.visibleInInspector ? "true" : "false"};\r\n`;
+            let finalOutput = `${variableName}.value = ${valueString};\r\n`;
+            finalOutput += `${variableName}.isConstant = ${this.isConstant ? "true" : "false"};\r\n`;
+            finalOutput += `${variableName}.visibleInInspector = ${this.visibleInInspector ? "true" : "false"};\r\n`;
 
             return finalOutput;
         }

+ 1 - 1
src/Materials/Node/nodeMaterialBlock.ts

@@ -541,7 +541,7 @@ export class NodeMaterialBlock {
 
         // Get unique name
         let nameAsVariableName = this.name.replace(/[^A-Za-z_]+/g, "");
-        this._codeVariableName = nameAsVariableName;
+        this._codeVariableName = nameAsVariableName || `${this.getClassName()}_${this.uniqueId}`;
 
         if (uniqueNames.indexOf(this._codeVariableName) !== -1) {
             let index = 0;

+ 1 - 1
src/Materials/PBR/pbrBaseMaterial.ts

@@ -1930,7 +1930,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         if (mustRebind || !this.isFrozen) {
             // Lights
             if (scene.lightsEnabled && !this._disableLighting) {
-                MaterialHelper.BindLights(scene, mesh, this._activeEffect, defines, this._maxSimultaneousLights, this._lightFalloff !== PBRBaseMaterial.LIGHTFALLOFF_STANDARD, this._rebuildInParallel);
+                MaterialHelper.BindLights(scene, mesh, this._activeEffect, defines, this._maxSimultaneousLights, this._rebuildInParallel);
             }
 
             // View

+ 4 - 6
src/Materials/materialHelper.ts

@@ -687,11 +687,10 @@ export class MaterialHelper {
      * @param scene The scene where the light belongs to
      * @param effect The effect we are binding the data to
      * @param useSpecular Defines if specular is supported
-     * @param usePhysicalLightFalloff Specifies whether the light falloff is defined physically or not
      * @param rebuildInParallel Specifies whether the shader is rebuilding in parallel
      */
-    public static BindLight(light: Light, lightIndex: number, scene: Scene, effect: Effect, useSpecular: boolean, usePhysicalLightFalloff = false, rebuildInParallel = false): void {
-        light.bindLight(lightIndex, scene, effect, useSpecular, usePhysicalLightFalloff, rebuildInParallel);
+    public static BindLight(light: Light, lightIndex: number, scene: Scene, effect: Effect, useSpecular: boolean, rebuildInParallel = false): void {
+        light._bindLight(lightIndex, scene, effect, useSpecular, rebuildInParallel);
     }
 
     /**
@@ -701,16 +700,15 @@ export class MaterialHelper {
      * @param effect The effect we are binding the data to
      * @param defines The generated defines for the effect
      * @param maxSimultaneousLights The maximum number of light that can be bound to the effect
-     * @param usePhysicalLightFalloff Specifies whether the light falloff is defined physically or not
      * @param rebuildInParallel Specifies whether the shader is rebuilding in parallel
      */
-    public static BindLights(scene: Scene, mesh: AbstractMesh, effect: Effect, defines: any, maxSimultaneousLights = 4, usePhysicalLightFalloff = false, rebuildInParallel = false): void {
+    public static BindLights(scene: Scene, mesh: AbstractMesh, effect: Effect, defines: any, maxSimultaneousLights = 4, rebuildInParallel = false): void {
         let len = Math.min(mesh.lightSources.length, maxSimultaneousLights);
 
         for (var i = 0; i < len; i++) {
 
             let light = mesh.lightSources[i];
-            this.BindLight(light, i, scene, effect, typeof defines === "boolean" ? defines : defines["SPECULARTERM"], usePhysicalLightFalloff, rebuildInParallel);
+            this.BindLight(light, i, scene, effect, typeof defines === "boolean" ? defines : defines["SPECULARTERM"], rebuildInParallel);
         }
     }
 

+ 1 - 1
src/Materials/standardMaterial.ts

@@ -1490,7 +1490,7 @@ export class StandardMaterial extends PushMaterial {
         if (mustRebind || !this.isFrozen) {
             // Lights
             if (scene.lightsEnabled && !this._disableLighting) {
-                MaterialHelper.BindLights(scene, mesh, effect, defines, this._maxSimultaneousLights, false, this._rebuildInParallel);
+                MaterialHelper.BindLights(scene, mesh, effect, defines, this._maxSimultaneousLights, this._rebuildInParallel);
             }
 
             // View

+ 5 - 5
src/Shaders/ShadersInclude/lightFragment.fx

@@ -50,7 +50,7 @@
             #ifdef HEMILIGHT{X}
                 preInfo.roughness = roughness;
             #else
-                preInfo.roughness = adjustRoughnessFromLightProperties(roughness, light{X}.vLightDiffuse.a, preInfo.lightDistance);
+                preInfo.roughness = adjustRoughnessFromLightProperties(roughness, light{X}.vLightSpecular.a, preInfo.lightDistance);
             #endif
 
             // Diffuse contribution
@@ -86,7 +86,7 @@
                 #ifdef HEMILIGHT{X}
                     preInfo.roughness = clearCoatRoughness;
                 #else
-                    preInfo.roughness = adjustRoughnessFromLightProperties(clearCoatRoughness, light{X}.vLightDiffuse.a, preInfo.lightDistance);
+                    preInfo.roughness = adjustRoughnessFromLightProperties(clearCoatRoughness, light{X}.vLightSpecular.a, preInfo.lightDistance);
                 #endif
 
                 info.clearCoat = computeClearCoatLighting(preInfo, clearCoatNormalW, clearCoatAARoughnessFactors.x, clearCoatIntensity, light{X}.vLightDiffuse.rgb);
@@ -111,11 +111,11 @@
             #endif
         #else
             #ifdef SPOTLIGHT{X}
-                info = computeSpotLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDirection, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightDiffuse.a, glossiness);
+                info = computeSpotLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDirection, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular.rgb, light{X}.vLightDiffuse.a, glossiness);
             #elif defined(HEMILIGHT{X})
-                info = computeHemisphericLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightGround, glossiness);
+                info = computeHemisphericLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular.rgb, light{X}.vLightGround, glossiness);
             #elif defined(POINTLIGHT{X}) || defined(DIRLIGHT{X})
-                info = computeLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightDiffuse.a, glossiness);
+                info = computeLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular.rgb, light{X}.vLightDiffuse.a, glossiness);
             #endif
         #endif
 

+ 2 - 2
src/Shaders/ShadersInclude/lightFragmentDeclaration.fx

@@ -3,9 +3,9 @@
 	uniform vec4 vLightDiffuse{X};
 
 	#ifdef SPECULARTERM
-		uniform vec3 vLightSpecular{X};
+		uniform vec4 vLightSpecular{X};
 	#else
-		vec3 vLightSpecular{X} = vec3(0.);
+		vec4 vLightSpecular{X} = vec4(0.);
 	#endif
 	#ifdef SHADOW{X}
 		#if defined(SHADOWCUBE{X})

+ 1 - 1
src/Shaders/ShadersInclude/lightUboDeclaration.fx

@@ -4,7 +4,7 @@
 		vec4 vLightData;
 
 		vec4 vLightDiffuse;
-		vec3 vLightSpecular;
+		vec4 vLightSpecular;
 		#ifdef SPOTLIGHT{X}
 			vec4 vLightDirection;
 			vec4 vLightFalloff;

+ 10 - 5
tests/validation/config.json

@@ -191,7 +191,8 @@
             "title": "The car",
             "sceneFolder": "/Scenes/TheCar/",
             "sceneFilename": "TheCar.binary.babylon",
-            "referenceImage": "TheCar.png"
+            "referenceImage": "TheCar.png",
+            "errorRatio": 5
         },
         {
             "title": "Viper",
@@ -228,7 +229,8 @@
             "title": "SpaceDeK",
             "sceneFolder": "/Scenes/SpaceDek/",
             "sceneFilename": "SpaceDek.babylon",
-            "referenceImage": "SpaceDeK.png"
+            "referenceImage": "SpaceDeK.png",
+            "errorRatio": 5
         },
         {
             "title": "Flat2009",
@@ -297,7 +299,8 @@
             "title": "LOD",
             "renderCount": 10,
             "playgroundId": "#FFMFW5#0",
-            "referenceImage": "lod.png"
+            "referenceImage": "lod.png",
+            "errorRatio": 5
         },
         {
             "title": "Lens",
@@ -532,12 +535,14 @@
         {
             "title": "GLTF Serializer with Negative World Matrix",
             "playgroundId": "#KX53VK#27",
-            "referenceImage": "glTFSerializerNegativeWorldMatrix.png"
+            "referenceImage": "glTFSerializerNegativeWorldMatrix.png",
+            "errorRatio": 1.1
         },
         {
             "title": "GLTF Serializer with Negative World Matrix (Right Handed)",
             "playgroundId": "#KX53VK#30",
-            "referenceImage": "glTFSerializerNegativeWorldMatrix_Right.png"
+            "referenceImage": "glTFSerializerNegativeWorldMatrix_Right.png",
+            "errorRatio": 1.1
         },
         {
             "title": "GLTF Buggy with Draco Mesh Compression",

+ 0 - 5
tests/validation/integration.js

@@ -26,11 +26,6 @@ xhr.addEventListener("load", function () {
                         var info = engine.getGlInfo();
                         console.log("Webgl Version: " + info.version);
                         console.log("Webgl Vendor: " + info.vendor);
-                        // Reduces error ratio on Embedded Firefox for travis.
-                        if (info.vendor === "VMware, Inc.") {
-                            errorRatio = 5;
-                        }
-
                         console.log("Webgl Renderer: " + info.renderer);
                         done();
                     });

+ 7 - 7
tests/validation/validation.js

@@ -6,9 +6,6 @@ var currentScene;
 var config;
 var justOnce;
 
-var threshold = 25;
-var errorRatio = 2.5;
-
 // Overload the random to make it deterministic
 var seed = 100000,
     constant = Math.pow(2, 13) + 1,
@@ -23,7 +20,7 @@ Math.random = function() {
     return seed / maximum;
 }
 
-function compare(renderData, referenceCanvas) {
+function compare(renderData, referenceCanvas, threshold, errorRatio) {
     var width = referenceCanvas.width;
     var height = referenceCanvas.height;
     var size = width * height * 4;
@@ -98,7 +95,7 @@ function saveRenderImage(data, canvas) {
     return screenshotCanvas.toDataURL();
 }
 
-function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done) {
+function evaluate(test, resultCanvas, result, renderImage, waitRing, done) {
     var renderData = getRenderData(canvas, engine);
     var testRes = true;
 
@@ -113,7 +110,10 @@ function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done
 
         // Visual check
         if (!test.onlyVisual) {
-            if (compare(renderData, resultCanvas)) {
+            var info = engine.getGlInfo();
+            var defaultErrorRatio = 2.5
+
+            if (compare(renderData, resultCanvas, test.threshold || 25, test.errorRatio || defaultErrorRatio)) {
                 result.classList.add("failed");
                 result.innerHTML = "×";
                 testRes = false;
@@ -153,7 +153,7 @@ function processCurrentScene(test, resultCanvas, result, renderImage, index, wai
 
                 if (renderCount === 0) {
                     engine.stopRenderLoop();
-                    evaluate(test, resultCanvas, result, renderImage, index, waitRing, done);
+                    evaluate(test, resultCanvas, result, renderImage, waitRing, done);
                 }
             }
             catch (e) {