瀏覽代碼

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

Jaskar 6 年之前
父節點
當前提交
ae782d9e29
共有 100 個文件被更改,包括 8987 次插入7563 次删除
  1. 782 502
      Playground/babylon.d.txt
  2. 1 0
      Tools/Config/config.json
  3. 15 15
      Tools/Gulp/helpers/gulp-validateImports.js
  4. 29 8
      Tools/Gulp/tasks/gulpTasks-libraries.js
  5. 1 0
      Tools/Publisher/tasks/processEs6Packages.js
  6. 2 2
      Tools/Publisher/tasks/versionNumberManager.js
  7. 790 508
      dist/preview release/babylon.d.ts
  8. 1 1
      dist/preview release/babylon.js
  9. 624 454
      dist/preview release/babylon.max.js
  10. 1 1
      dist/preview release/babylon.max.js.map
  11. 1535 1626
      dist/preview release/babylon.module.d.ts
  12. 802 521
      dist/preview release/documentation.d.ts
  13. 1 1
      dist/preview release/glTF2Interface/package.json
  14. 40 40
      dist/preview release/gui/babylon.gui.js
  15. 1 1
      dist/preview release/gui/babylon.gui.js.map
  16. 2 2
      dist/preview release/gui/package.json
  17. 7 7
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  18. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  19. 6 6
      dist/preview release/inspector/package.json
  20. 3 8
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  21. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  22. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  23. 3 8
      dist/preview release/loaders/babylon.glTFFileLoader.js
  24. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  25. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  26. 3 8
      dist/preview release/loaders/babylonjs.loaders.js
  27. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  28. 2 2
      dist/preview release/loaders/babylonjs.loaders.min.js
  29. 3 3
      dist/preview release/loaders/package.json
  30. 2 2
      dist/preview release/materialsLibrary/package.json
  31. 11 10
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  32. 2 2
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  33. 106 137
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  34. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  35. 27 24
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  36. 2 2
      dist/preview release/nodeEditor/package.json
  37. 1 1
      dist/preview release/package.json
  38. 1 1
      dist/preview release/packagesSizeBaseLine.json
  39. 2 2
      dist/preview release/postProcessesLibrary/package.json
  40. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  41. 3 3
      dist/preview release/serializers/package.json
  42. 1535 1626
      dist/preview release/viewer/babylon.module.d.ts
  43. 49 49
      dist/preview release/viewer/babylon.viewer.js
  44. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  45. 11 1
      dist/preview release/what's new.md
  46. 6 6
      inspector/src/inspector.ts
  47. 0 2
      loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts
  48. 0 1
      loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts
  49. 4 5
      loaders/src/glTF/2.0/glTFLoader.ts
  50. 746 1186
      localDev/src/webgl-debug.js
  51. 1 33
      nodeEditor/src/components/diagram/defaultNodeModel.ts
  52. 71 19
      nodeEditor/src/components/diagram/diagram.scss
  53. 8 37
      nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx
  54. 5 6
      nodeEditor/src/components/diagram/input/inputNodeModel.tsx
  55. 26 41
      nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx
  56. 28 49
      nodeEditor/src/components/diagram/input/inputNodeWidget.tsx
  57. 2 2
      nodeEditor/src/components/diagram/light/lightNodeModel.tsx
  58. 4 35
      nodeEditor/src/components/diagram/light/lightNodeWidget.tsx
  59. 61 0
      nodeEditor/src/components/diagram/portHelper.tsx
  60. 6 6
      nodeEditor/src/components/diagram/texture/textureNodeModel.tsx
  61. 4 33
      nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx
  62. 3 2
      nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx
  63. 18 0
      nodeEditor/src/components/nodeList/nodeList.scss
  64. 8 37
      nodeEditor/src/components/nodeList/nodeListComponent.tsx
  65. 3 3
      nodeEditor/src/components/propertyTab/properties/color3PropertyTabComponent.tsx
  66. 3 3
      nodeEditor/src/components/propertyTab/properties/floatPropertyTabComponent.tsx
  67. 3 3
      nodeEditor/src/components/propertyTab/properties/vector2PropertyTabComponent.tsx
  68. 3 3
      nodeEditor/src/components/propertyTab/properties/vector3PropertyTabComponent.tsx
  69. 3 0
      nodeEditor/src/components/propertyTab/propertyTabComponent.tsx
  70. 1 0
      nodeEditor/src/globalState.ts
  71. 245 93
      nodeEditor/src/graphEditor.tsx
  72. 7 0
      nodeEditor/src/main.scss
  73. 23 0
      nodeEditor/src/sharedComponents/draggableLineComponent.tsx
  74. 4 3
      package.json
  75. 2 1
      serializers/src/glTF/2.0/glTFMaterialExporter.ts
  76. 4 0
      src/Cameras/XR/index.ts
  77. 16 14
      src/Cameras/XR/webXRCamera.ts
  78. 116 0
      src/Cameras/XR/webXRController.ts
  79. 44 0
      src/Cameras/XR/webXRControllerModelLoader.ts
  80. 119 0
      src/Cameras/XR/webXRControllerPointerSelection.ts
  81. 181 0
      src/Cameras/XR/webXRControllerTeleportation.ts
  82. 104 0
      src/Cameras/XR/webXRDefaultExperience.ts
  83. 13 16
      src/Cameras/XR/webXREnterExitUI.ts
  84. 37 40
      src/Cameras/XR/webXRExperienceHelper.ts
  85. 74 77
      src/Cameras/XR/webXRInput.ts
  86. 27 8
      src/Cameras/XR/webXRManagedOutputCanvas.ts
  87. 92 89
      src/Cameras/XR/webXRSessionManager.ts
  88. 1 1
      src/Cameras/camera.ts
  89. 33 3
      src/Culling/ray.ts
  90. 10 2
      src/Engines/engine.ts
  91. 23 8
      src/Gamepads/Controllers/poseEnabledController.ts
  92. 8 15
      src/Helpers/sceneHelpers.ts
  93. 24 4
      src/Layers/effectLayer.ts
  94. 106 30
      src/LibDeclarations/webxr.d.ts
  95. 21 26
      src/Materials/Node/Blocks/Dual/fogBlock.ts
  96. 2 1
      src/Materials/Node/Blocks/Dual/index.ts
  97. 28 23
      src/Materials/Node/Blocks/Dual/lightBlock.ts
  98. 185 0
      src/Materials/Node/Blocks/Dual/textureBlock.ts
  99. 4 2
      src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts
  100. 0 0
      src/Materials/Node/Blocks/Fragment/index.ts

文件差異過大導致無法顯示
+ 782 - 502
Playground/babylon.d.txt


+ 1 - 0
Tools/Config/config.json

@@ -596,6 +596,7 @@
             "uncheckedLintImports": [
                 "react",
                 "react-dom",
+                "dagre",
                 "re-resizable",
                 "glTF"
             ],

+ 15 - 15
Tools/Gulp/helpers/gulp-validateImports.js

@@ -10,7 +10,7 @@ var config = require("../../Config/config");
 const indexExlclusion = ["States", "EmitterTypes"];
 const forbiddenImports = ["meshBuilder"];
 
-const mapping = { };
+const mapping = {};
 config.modules.forEach(moduleName => {
     mapping[config[moduleName].build.umd.packageName] = moduleName;
 });
@@ -196,7 +196,7 @@ var validateImports = function(data, fileLocation, options) {
 function gulpValidateImports(options) {
     var globalErrors = [];
 
-    return through.obj(function (file, enc, cb) {
+    return through.obj(function(file, enc, cb) {
         if (file.isNull()) {
             cb(null, file);
             return;
@@ -204,7 +204,7 @@ function gulpValidateImports(options) {
         if (file.isStream()) {
             cb(new PluginError("Validate imports", "Streaming not supported."));
         }
-        
+
         let data = file.contents.toString();
         let result = validateImports(data, file.path, options);
 
@@ -218,20 +218,20 @@ function gulpValidateImports(options) {
         }
 
         return cb();
-    }, 
-    function endStream(cb) {
-        if (globalErrors.length > 0) {
-            for (let error of globalErrors) {
-                colorConsole.error(error.message + " " + error.path);
-            }
-            colorConsole.error(`Import validation failed with ${globalErrors.length} errors.`);
+    },
+        function endStream(cb) {
+            if (globalErrors.length > 0) {
+                for (let error of globalErrors) {
+                    colorConsole.error(error.message + " " + error.path);
+                }
+                colorConsole.error(`Import validation failed with ${globalErrors.length} errors.`);
 
-            var finalMessage = new PluginError('gulp-validateImports', `gulp-validateImports: ${globalErrors.length} errors found.`);
-            this.emit('error', finalMessage);
-        }
+                var finalMessage = new PluginError('gulp-validateImports', `gulp-validateImports: ${globalErrors.length} errors found.`);
+                this.emit('error', finalMessage);
+            }
 
-        cb();
-    });
+            cb();
+        });
 }
 
 module.exports = gulpValidateImports;

+ 29 - 8
Tools/Gulp/tasks/gulpTasks-libraries.js

@@ -106,11 +106,24 @@ var buildAMDDTSFiles = function(libraries, settings, cb) {
 /**
  * Append Lose DTS Files allowing isolated Modules build
  */
-var appendLoseDTSFiles = function(settings) {
+var appendLoseDTSFiles = function(settings, moduleFile) {
     if (settings.build.loseDTSFiles) {
-        return gulp.src([config.computed.tempTypingsFilePath, path.join(settings.computed.srcDirectory, settings.build.loseDTSFiles.glob)])
-            .pipe(concat(config.computed.tempTypingsFileName))
-            .pipe(gulp.dest(config.computed.tempFolder));
+        let library = settings.libraries[0];
+        if (!library.preventLoadLibrary) {
+            // Convert Module to Namespace for globals
+            var outputDirectory = settings.computed.distDirectory;
+
+            // Find declaration path.
+            let fileName = settings.build.umd.processDeclaration.filename;
+            if (!moduleFile) {
+                fileName = fileName.replace(".module", "");
+            }
+
+            let fileLocation = path.join(outputDirectory, fileName);
+            return gulp.src([fileLocation, path.join(settings.computed.srcDirectory, settings.build.loseDTSFiles.glob)])
+                .pipe(concat(fileName))
+                .pipe(gulp.dest(outputDirectory));
+        }
     }
     return Promise.resolve();
 }
@@ -139,10 +152,15 @@ var processDTSFiles = function(libraries, settings, cb) {
 
         // Convert Module to Namespace for globals
         if (!commandLineOptions.noNamespace) {
-            processModuleDeclarationToNamespace(fileLocation, settings.build.umd.packageName, settings.build.umd.processDeclaration);
+            processModuleDeclarationToNamespace(fileLocation, settings.build.umd.packageName, settings.build.umd.processDeclaration, cb);
+        }
+        else {
+            cb();
         }
     }
-    cb();
+    else {
+        cb();
+    }
 }
 
 /**
@@ -158,10 +176,13 @@ function buildExternalLibraries(settings) {
     var buildMax = function() { return buildExternalLibrariesMultiEntry(settings.libraries, settings, false) };
 
     var buildAMDDTS = function(cb) { return buildAMDDTSFiles(settings.libraries, settings, cb) };
-    var appendLoseDTS = function() { return appendLoseDTSFiles(settings) };
     var processDTS = function(cb) { return processDTSFiles(settings.libraries, settings, cb) };
+    var appendLoseDTS = [function() { return appendLoseDTSFiles(settings, true) }];
+    if (!commandLineOptions.noNamespace) {
+        appendLoseDTS.push(function() { return appendLoseDTSFiles(settings, false) });
+    }
 
-    tasks.push(cleanup, shaders, buildMin, buildMax, buildAMDDTS, appendLoseDTS, processDTS);
+    tasks.push(cleanup, shaders, buildMin, buildMax, buildAMDDTS, processDTS, ...appendLoseDTS);
 
     return gulp.series.apply(this, tasks);
 }

+ 1 - 0
Tools/Publisher/tasks/processEs6Packages.js

@@ -98,6 +98,7 @@ function processEs6Packages(version) {
         var mainPackageJSON = fs.readJSONSync(mainPackageJSONPath);
         var tslibSemver = mainPackageJSON["devDependencies"]["tslib"];
         colorConsole.log("    Adding tslib version: ", tslibSemver.yellow);
+        umdPackageJson["dependencies"] = umdPackageJson["dependencies"] || {};
         umdPackageJson["dependencies"]["tslib"] = tslibSemver;
 
         let packageJSONPath = path.join(packagePath, "package.json");

+ 2 - 2
Tools/Publisher/tasks/versionNumberManager.js

@@ -11,10 +11,10 @@ const enginePath = path.join(config.core.computed.mainDirectory, "Engines/engine
  * Get the version from the engine class for Babylon
  */
 function getEngineVersion() {
-    colorConsole.log("Get version from engine.ts");
+    colorConsole.log("Get version from engine.ts", enginePath);
     const engineContent = fs.readFileSync(enginePath).toString();
 
-    const versionRegex = new RegExp(`public static get Version\\(\\): string {[\\s\\S]*return "([\\s\\S]*?)";[\\s\\S]*}`, "gm");
+    const versionRegex = new RegExp(`public static get Version\\(\\): string {\\s*return "(\\S*)";\\s*}`, "gm");
     const match = versionRegex.exec(engineContent);
     if (match && match.length) {
         const version = match[1];

文件差異過大導致無法顯示
+ 790 - 508
dist/preview release/babylon.d.ts


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/babylon.js


文件差異過大導致無法顯示
+ 624 - 454
dist/preview release/babylon.max.js


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/babylon.max.js.map


文件差異過大導致無法顯示
+ 1535 - 1626
dist/preview release/babylon.module.d.ts


文件差異過大導致無法顯示
+ 802 - 521
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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 40 - 40
dist/preview release/gui/babylon.gui.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-gui"] = factory(require("babylonjs"));
 	else
 		root["BABYLON"] = root["BABYLON"] || {}, root["BABYLON"]["GUI"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -355,7 +355,7 @@ module.exports = g;
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTextureInstrumentation", function() { return AdvancedDynamicTextureInstrumentation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -498,7 +498,7 @@ var AdvancedDynamicTextureInstrumentation = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTexture", function() { return AdvancedDynamicTexture; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _controls_container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controls/container */ "./2D/controls/container.ts");
 /* harmony import */ var _style__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./style */ "./2D/style.ts");
@@ -1619,7 +1619,7 @@ var Button = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Checkbox", function() { return Checkbox; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -1800,7 +1800,7 @@ var Checkbox = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ColorPicker", function() { return ColorPicker; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
@@ -3187,7 +3187,7 @@ var ColorPicker = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -3592,7 +3592,7 @@ var Container = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control", function() { return Control; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -5783,7 +5783,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__);
 
 
@@ -6239,7 +6239,7 @@ var Grid = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Image", function() { return Image; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 
@@ -7014,7 +7014,7 @@ var InputPassword = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputText", function() { return InputText; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8023,7 +8023,7 @@ var InputText = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Line", function() { return Line; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8291,7 +8291,7 @@ var Line = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLine", function() { return MultiLine; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _multiLinePoint__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../multiLinePoint */ "./2D/multiLinePoint.ts");
@@ -8558,7 +8558,7 @@ var MultiLine = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RadioButton", function() { return RadioButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -8903,7 +8903,7 @@ var Rectangle = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScrollViewer", function() { return ScrollViewer; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Events/pointerEvents */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Events/pointerEvents */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Events_pointerEvents__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _rectangle__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../rectangle */ "./2D/controls/rectangle.ts");
 /* harmony import */ var _grid__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../grid */ "./2D/controls/grid.ts");
@@ -9994,7 +9994,7 @@ var SelectionPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BaseSlider", function() { return BaseSlider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -10895,7 +10895,7 @@ var Slider = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel", function() { return StackPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -11153,7 +11153,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextWrapping", function() { return TextWrapping; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextBlock", function() { return TextBlock; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -11593,7 +11593,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KeyPropertySet", function() { return KeyPropertySet; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualKeyboard", function() { return VirtualKeyboard; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
 /* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./button */ "./2D/controls/button.ts");
@@ -11974,7 +11974,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector2WithInfo", function() { return Vector2WithInfo; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Matrix2D", function() { return Matrix2D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -12198,7 +12198,7 @@ var Matrix2D = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Measure", function() { return Measure; });
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* 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__);
 
 
@@ -12331,7 +12331,7 @@ var Measure = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLinePoint", function() { return MultiLinePoint; });
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* 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 _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -12474,7 +12474,7 @@ var MultiLinePoint = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Style", function() { return Style; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -12781,7 +12781,7 @@ var ValueAndUnit = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AbstractButton3D", function() { return AbstractButton3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -12824,7 +12824,7 @@ var AbstractButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Button3D", function() { return Button3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _abstractButton3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./abstractButton3D */ "./3D/controls/abstractButton3D.ts");
 /* harmony import */ var _2D_advancedDynamicTexture__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../2D/advancedDynamicTexture */ "./2D/advancedDynamicTexture.ts");
@@ -13001,7 +13001,7 @@ var Button3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container3D", function() { return Container3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -13158,7 +13158,7 @@ var Container3D = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control3D", function() { return Control3D; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../vector3WithInfo */ "./3D/vector3WithInfo.ts");
 
@@ -13564,7 +13564,7 @@ var Control3D = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CylinderPanel", function() { return CylinderPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -13649,7 +13649,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HolographicButton", function() { return HolographicButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _button3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./button3D */ "./3D/controls/button3D.ts");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_2__);
 /* harmony import */ var _materials_fluentMaterial__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../materials/fluentMaterial */ "./3D/materials/fluentMaterial.ts");
 /* harmony import */ var _2D_controls_stackPanel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../2D/controls/stackPanel */ "./2D/controls/stackPanel.ts");
@@ -14125,7 +14125,7 @@ var MeshButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PlanePanel", function() { return PlanePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
@@ -14180,7 +14180,7 @@ var PlanePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScatterPanel", function() { return ScatterPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -14307,7 +14307,7 @@ var ScatterPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SpherePanel", function() { return SpherePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -14392,7 +14392,7 @@ var SpherePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel3D", function() { return StackPanel3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -14517,7 +14517,7 @@ var StackPanel3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VolumeBasedPanel", function() { return VolumeBasedPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -14708,7 +14708,7 @@ var VolumeBasedPanel = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GUI3DManager", function() { return GUI3DManager; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _controls_container3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./controls/container3D */ "./3D/controls/container3D.ts");
 
@@ -14975,7 +14975,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterialDefines", function() { return FluentMaterialDefines; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterial", function() { return FluentMaterial; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _shaders_fluent_vertex__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./shaders/fluent.vertex */ "./3D/materials/shaders/fluent.vertex.ts");
 /* harmony import */ var _shaders_fluent_fragment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./shaders/fluent.fragment */ "./3D/materials/shaders/fluent.fragment.ts");
@@ -15297,7 +15297,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentPixelShader", function() { return fluentPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentPixelShader';
@@ -15319,7 +15319,7 @@ var fluentPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentVertexShader", function() { return fluentVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentVertexShader';
@@ -15342,7 +15342,7 @@ var fluentVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector3WithInfo", function() { return Vector3WithInfo; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -15636,14 +15636,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


+ 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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7"
     },
     "engines": {
         "node": "*"

+ 7 - 7
dist/preview release/inspector/babylon.inspector.bundle.max.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-inspector"] = factory(require("babylonjs-gui"), require("babylonjs-loaders"), require("babylonjs-serializers"), require("babylonjs"));
 	else
 		root["INSPECTOR"] = factory(root["BABYLON"]["GUI"], root["BABYLON"], root["BABYLON"], root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_gui_2D_adtInstrumentation__, __WEBPACK_EXTERNAL_MODULE_babylonjs_loaders_glTF_index__, __WEBPACK_EXTERNAL_MODULE_babylonjs_serializers_glTF_2_0_index__, __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_gui_2D_controls_image__, __WEBPACK_EXTERNAL_MODULE_babylonjs_loaders_glTF_index__, __WEBPACK_EXTERNAL_MODULE_babylonjs_serializers_glTF_2_0_index__, __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -36153,7 +36153,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _lineContainerComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../lineContainerComponent */ "./components/actionTabs/lineContainerComponent.tsx");
 /* harmony import */ var _lines_textLineComponent__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../lines/textLineComponent */ "./components/actionTabs/lines/textLineComponent.tsx");
-/* harmony import */ var babylonjs_gui_2D_controls_control__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs-gui/2D/controls/control */ "babylonjs-gui/2D/adtInstrumentation");
+/* harmony import */ var babylonjs_gui_2D_controls_control__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs-gui/2D/controls/control */ "babylonjs-gui/2D/controls/image");
 /* harmony import */ var babylonjs_gui_2D_controls_control__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(babylonjs_gui_2D_controls_control__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var _lines_sliderLineComponent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../lines/sliderLineComponent */ "./components/actionTabs/lines/sliderLineComponent.tsx");
 /* harmony import */ var _lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../lines/floatLineComponent */ "./components/actionTabs/lines/floatLineComponent.tsx");
@@ -36458,7 +36458,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _commonControlPropertyGridComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./commonControlPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx");
 /* harmony import */ var _lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../lineContainerComponent */ "./components/actionTabs/lineContainerComponent.tsx");
-/* harmony import */ var babylonjs_gui_2D_controls_image__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs-gui/2D/controls/image */ "babylonjs-gui/2D/adtInstrumentation");
+/* harmony import */ var babylonjs_gui_2D_controls_image__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs-gui/2D/controls/image */ "babylonjs-gui/2D/controls/image");
 /* harmony import */ var babylonjs_gui_2D_controls_image__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(babylonjs_gui_2D_controls_image__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var _lines_floatLineComponent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../lines/floatLineComponent */ "./components/actionTabs/lines/floatLineComponent.tsx");
 /* harmony import */ var _lines_checkBoxLineComponent__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../../lines/checkBoxLineComponent */ "./components/actionTabs/lines/checkBoxLineComponent.tsx");
@@ -36872,7 +36872,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react */ "../../node_modules/react/index.js");
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _commonControlPropertyGridComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./commonControlPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx");
-/* harmony import */ var babylonjs_gui_2D_controls_textBlock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs-gui/2D/controls/textBlock */ "babylonjs-gui/2D/adtInstrumentation");
+/* harmony import */ var babylonjs_gui_2D_controls_textBlock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs-gui/2D/controls/textBlock */ "babylonjs-gui/2D/controls/image");
 /* harmony import */ var babylonjs_gui_2D_controls_textBlock__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_gui_2D_controls_textBlock__WEBPACK_IMPORTED_MODULE_3__);
 /* harmony import */ var _lineContainerComponent__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../lineContainerComponent */ "./components/actionTabs/lineContainerComponent.tsx");
 /* harmony import */ var _lines_textInputLineComponent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../../lines/textInputLineComponent */ "./components/actionTabs/lines/textInputLineComponent.tsx");
@@ -38009,7 +38009,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _lines_optionsLineComponent__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../../../lines/optionsLineComponent */ "./components/actionTabs/lines/optionsLineComponent.tsx");
 /* harmony import */ var _lines_fileButtonLineComponent__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../../../lines/fileButtonLineComponent */ "./components/actionTabs/lines/fileButtonLineComponent.tsx");
 /* harmony import */ var _lines_valueLineComponent__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ../../../lines/valueLineComponent */ "./components/actionTabs/lines/valueLineComponent.tsx");
-/* harmony import */ var babylonjs_gui_2D_adtInstrumentation__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! babylonjs-gui/2D/adtInstrumentation */ "babylonjs-gui/2D/adtInstrumentation");
+/* harmony import */ var babylonjs_gui_2D_adtInstrumentation__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! babylonjs-gui/2D/adtInstrumentation */ "babylonjs-gui/2D/controls/image");
 /* harmony import */ var babylonjs_gui_2D_adtInstrumentation__WEBPACK_IMPORTED_MODULE_12___default = /*#__PURE__*/__webpack_require__.n(babylonjs_gui_2D_adtInstrumentation__WEBPACK_IMPORTED_MODULE_12__);
 /* harmony import */ var _customPropertyGridComponent__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../customPropertyGridComponent */ "./components/actionTabs/tabs/propertyGrids/customPropertyGridComponent.tsx");
 
@@ -42699,14 +42699,14 @@ var Tools = /** @class */ (function () {
 
 /***/ }),
 
-/***/ "babylonjs-gui/2D/adtInstrumentation":
+/***/ "babylonjs-gui/2D/controls/image":
 /*!************************************************************************************************************************!*\
   !*** external {"root":["BABYLON","GUI"],"commonjs":"babylonjs-gui","commonjs2":"babylonjs-gui","amd":"babylonjs-gui"} ***!
   \************************************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_gui_2D_adtInstrumentation__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_gui_2D_controls_image__;
 
 /***/ }),
 

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


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

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

+ 3 - 8
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -567,13 +567,11 @@ var KHR_materials_pbrSpecularGlossiness = /** @class */ (function () {
         babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
         if (properties.diffuseTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Diffuse)";
                 babylonMaterial.albedoTexture = texture;
             }));
         }
         if (properties.specularGlossinessTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Specular Glossiness)";
                 babylonMaterial.reflectivityTexture = texture;
             }));
             babylonMaterial.reflectivityTexture.hasAlpha = true;
@@ -646,7 +644,6 @@ var KHR_materials_unlit = /** @class */ (function () {
             }
             if (properties.baseColorTexture) {
                 promises.push(this._loader.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
@@ -2749,13 +2746,11 @@ var GLTFLoader = /** @class */ (function () {
             babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
             if (properties.baseColorTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
             if (properties.metallicRoughnessTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/metallicRoughnessTexture", properties.metallicRoughnessTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Metallic Roughness)";
                     babylonMaterial.metallicTexture = texture;
                 }));
                 babylonMaterial.useMetallnessFromMetallicTextureBlue = true;
@@ -2866,7 +2861,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.normalTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/normalTexture", material.normalTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Normal)";
                 babylonMaterial.bumpTexture = texture;
             }));
             babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem;
@@ -2878,7 +2872,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.occlusionTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/occlusionTexture", material.occlusionTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Occlusion)";
                 babylonMaterial.ambientTexture = texture;
             }));
             babylonMaterial.useAmbientInGrayScale = true;
@@ -2888,7 +2881,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.emissiveTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/emissiveTexture", material.emissiveTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Emissive)";
                 babylonMaterial.emissiveTexture = texture;
             }));
         }
@@ -2950,6 +2942,9 @@ var GLTFLoader = /** @class */ (function () {
         var texture = ArrayItem.Get(context + "/index", this._gltf.textures, textureInfo.index);
         var promise = this._loadTextureAsync("/textures/" + textureInfo.index, texture, function (babylonTexture) {
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
+            if (texture.name) {
+                babylonTexture.name = texture.name;
+            }
             GLTFLoader.AddPointerMetadata(babylonTexture, context);
             _this._parent.onTextureLoadedObservable.notifyObservers(babylonTexture);
             assign(babylonTexture);

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.js.map


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 3 - 8
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3116,13 +3116,11 @@ var KHR_materials_pbrSpecularGlossiness = /** @class */ (function () {
         babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
         if (properties.diffuseTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Diffuse)";
                 babylonMaterial.albedoTexture = texture;
             }));
         }
         if (properties.specularGlossinessTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Specular Glossiness)";
                 babylonMaterial.reflectivityTexture = texture;
             }));
             babylonMaterial.reflectivityTexture.hasAlpha = true;
@@ -3195,7 +3193,6 @@ var KHR_materials_unlit = /** @class */ (function () {
             }
             if (properties.baseColorTexture) {
                 promises.push(this._loader.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
@@ -5298,13 +5295,11 @@ var GLTFLoader = /** @class */ (function () {
             babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
             if (properties.baseColorTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
             if (properties.metallicRoughnessTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/metallicRoughnessTexture", properties.metallicRoughnessTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Metallic Roughness)";
                     babylonMaterial.metallicTexture = texture;
                 }));
                 babylonMaterial.useMetallnessFromMetallicTextureBlue = true;
@@ -5415,7 +5410,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.normalTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/normalTexture", material.normalTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Normal)";
                 babylonMaterial.bumpTexture = texture;
             }));
             babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem;
@@ -5427,7 +5421,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.occlusionTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/occlusionTexture", material.occlusionTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Occlusion)";
                 babylonMaterial.ambientTexture = texture;
             }));
             babylonMaterial.useAmbientInGrayScale = true;
@@ -5437,7 +5430,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.emissiveTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/emissiveTexture", material.emissiveTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Emissive)";
                 babylonMaterial.emissiveTexture = texture;
             }));
         }
@@ -5499,6 +5491,9 @@ var GLTFLoader = /** @class */ (function () {
         var texture = ArrayItem.Get(context + "/index", this._gltf.textures, textureInfo.index);
         var promise = this._loadTextureAsync("/textures/" + textureInfo.index, texture, function (babylonTexture) {
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
+            if (texture.name) {
+                babylonTexture.name = texture.name;
+            }
             GLTFLoader.AddPointerMetadata(babylonTexture, context);
             _this._parent.onTextureLoadedObservable.notifyObservers(babylonTexture);
             assign(babylonTexture);

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.js.map


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 3 - 8
dist/preview release/loaders/babylonjs.loaders.js

@@ -4458,13 +4458,11 @@ var KHR_materials_pbrSpecularGlossiness = /** @class */ (function () {
         babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
         if (properties.diffuseTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Diffuse)";
                 babylonMaterial.albedoTexture = texture;
             }));
         }
         if (properties.specularGlossinessTexture) {
             promises.push(this._loader.loadTextureInfoAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Specular Glossiness)";
                 babylonMaterial.reflectivityTexture = texture;
             }));
             babylonMaterial.reflectivityTexture.hasAlpha = true;
@@ -4537,7 +4535,6 @@ var KHR_materials_unlit = /** @class */ (function () {
             }
             if (properties.baseColorTexture) {
                 promises.push(this._loader.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
@@ -6640,13 +6637,11 @@ var GLTFLoader = /** @class */ (function () {
             babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
             if (properties.baseColorTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/baseColorTexture", properties.baseColorTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Base Color)";
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
             if (properties.metallicRoughnessTexture) {
                 promises.push(this.loadTextureInfoAsync(context + "/metallicRoughnessTexture", properties.metallicRoughnessTexture, function (texture) {
-                    texture.name = babylonMaterial.name + " (Metallic Roughness)";
                     babylonMaterial.metallicTexture = texture;
                 }));
                 babylonMaterial.useMetallnessFromMetallicTextureBlue = true;
@@ -6757,7 +6752,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.normalTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/normalTexture", material.normalTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Normal)";
                 babylonMaterial.bumpTexture = texture;
             }));
             babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem;
@@ -6769,7 +6763,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.occlusionTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/occlusionTexture", material.occlusionTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Occlusion)";
                 babylonMaterial.ambientTexture = texture;
             }));
             babylonMaterial.useAmbientInGrayScale = true;
@@ -6779,7 +6772,6 @@ var GLTFLoader = /** @class */ (function () {
         }
         if (material.emissiveTexture) {
             promises.push(this.loadTextureInfoAsync(context + "/emissiveTexture", material.emissiveTexture, function (texture) {
-                texture.name = babylonMaterial.name + " (Emissive)";
                 babylonMaterial.emissiveTexture = texture;
             }));
         }
@@ -6841,6 +6833,9 @@ var GLTFLoader = /** @class */ (function () {
         var texture = ArrayItem.Get(context + "/index", this._gltf.textures, textureInfo.index);
         var promise = this._loadTextureAsync("/textures/" + textureInfo.index, texture, function (babylonTexture) {
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
+            if (texture.name) {
+                babylonTexture.name = texture.name;
+            }
             GLTFLoader.AddPointerMetadata(babylonTexture, context);
             _this._parent.onTextureLoadedObservable.notifyObservers(babylonTexture);
             assign(babylonTexture);

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/loaders/babylonjs.loaders.min.js


+ 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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "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-alpha.6",
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs-gltf2interface": "4.1.0-alpha.7",
+        "babylonjs": "4.1.0-alpha.7"
     },
     "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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7"
     },
     "engines": {
         "node": "*"

+ 11 - 10
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts

@@ -265,14 +265,14 @@ declare module NODEEDITOR {
 }
 declare module NODEEDITOR {
     /**
-     * BABYLON.Texture node model which stores information about a node editor block
+     * Texture node model which stores information about a node editor block
      */
     export class TextureNodeModel extends DefaultNodeModel {
         private _block;
         /**
-         * BABYLON.Texture for the node if it exists
+         * Texture for the node if it exists
          */
-        texture: BABYLON.Nullable<BABYLON.Texture>;
+        texture: BABYLON.Nullable<BABYLON.BaseTexture>;
         /**
          * Constructs the node model
          */
@@ -408,7 +408,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IVector2PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Vector2PropertyTabComponent extends React.Component<IVector2PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -448,7 +448,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IVector3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Vector3PropertyTabComponent extends React.Component<IVector3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -514,7 +514,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IColor3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Color3PropertyTabComponent extends React.Component<IColor3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -549,7 +549,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IFloatPropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class FloatPropertyTabComponent extends React.Component<IFloatPropertyTabComponentProps> {
         render(): JSX.Element;
@@ -581,12 +581,13 @@ declare module NODEEDITOR {
      * Generic node model which stores information about a node editor block
      */
     export class InputNodeModel extends DefaultNodeModel {
-        connection?: BABYLON.NodeMaterialConnectionPoint;
+        outputType: BABYLON.NodeMaterialBlockConnectionPointTypes;
+        readonly inputBlock: BABYLON.InputBlock;
         /**
          * Constructs the node model
          */
         constructor();
-        renderProperties(globalState: GlobalState): JSX.Element | null;
+        renderProperties(globalState: GlobalState): JSX.Element;
     }
 }
 declare module NODEEDITOR {
@@ -729,7 +730,7 @@ declare module NODEEDITOR {
     }
     export class NodeCreationOptions {
         column: number;
-        nodeMaterialBlock?: BABYLON.NodeMaterialBlock;
+        nodeMaterialBlock: BABYLON.NodeMaterialBlock;
         type?: string;
         connection?: BABYLON.NodeMaterialConnectionPoint;
     }

文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/nodeEditor/babylon.nodeEditor.js


文件差異過大導致無法顯示
+ 106 - 137
dist/preview release/nodeEditor/babylon.nodeEditor.max.js


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


+ 27 - 24
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts

@@ -304,7 +304,7 @@ declare module "babylonjs-node-editor/components/diagram/texture/textureProperty
 }
 declare module "babylonjs-node-editor/components/diagram/texture/textureNodeModel" {
     import { Nullable } from 'babylonjs/types';
-    import { Texture } from 'babylonjs/Materials/Textures/texture';
+    import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
     import { DefaultNodeModel } from "babylonjs-node-editor/components/diagram/defaultNodeModel";
     import { GlobalState } from "babylonjs-node-editor/globalState";
     import { NodeCreationOptions, GraphEditor } from "babylonjs-node-editor/graphEditor";
@@ -317,7 +317,7 @@ declare module "babylonjs-node-editor/components/diagram/texture/textureNodeMode
         /**
          * Texture for the node if it exists
          */
-        texture: Nullable<Texture>;
+        texture: Nullable<BaseTexture>;
         /**
          * Constructs the node model
          */
@@ -467,10 +467,10 @@ declare module "babylonjs-node-editor/sharedComponents/vector2LineComponent" {
 declare module "babylonjs-node-editor/components/propertyTab/properties/vector2PropertyTabComponent" {
     import * as React from "react";
     import { GlobalState } from "babylonjs-node-editor/globalState";
-    import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+    import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
     interface IVector2PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: NodeMaterialConnectionPoint;
+        inputBlock: InputBlock;
     }
     export class Vector2PropertyTabComponent extends React.Component<IVector2PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -514,10 +514,10 @@ declare module "babylonjs-node-editor/sharedComponents/vector3LineComponent" {
 declare module "babylonjs-node-editor/components/propertyTab/properties/vector3PropertyTabComponent" {
     import * as React from "react";
     import { GlobalState } from "babylonjs-node-editor/globalState";
-    import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+    import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
     interface IVector3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: NodeMaterialConnectionPoint;
+        inputBlock: InputBlock;
     }
     export class Vector3PropertyTabComponent extends React.Component<IVector3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -590,10 +590,10 @@ declare module "babylonjs-node-editor/sharedComponents/color3LineComponent" {
 declare module "babylonjs-node-editor/components/propertyTab/properties/color3PropertyTabComponent" {
     import * as React from "react";
     import { GlobalState } from "babylonjs-node-editor/globalState";
-    import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+    import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
     interface IColor3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: NodeMaterialConnectionPoint;
+        inputBlock: InputBlock;
     }
     export class Color3PropertyTabComponent extends React.Component<IColor3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -631,10 +631,10 @@ declare module "babylonjs-node-editor/sharedComponents/floatLineComponent" {
 declare module "babylonjs-node-editor/components/propertyTab/properties/floatPropertyTabComponent" {
     import * as React from "react";
     import { GlobalState } from "babylonjs-node-editor/globalState";
-    import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+    import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
     interface IFloatPropertyTabComponentProps {
         globalState: GlobalState;
-        connection: NodeMaterialConnectionPoint;
+        inputBlock: InputBlock;
     }
     export class FloatPropertyTabComponent extends React.Component<IFloatPropertyTabComponentProps> {
         render(): JSX.Element;
@@ -667,18 +667,20 @@ declare module "babylonjs-node-editor/components/diagram/input/inputNodeProperty
 }
 declare module "babylonjs-node-editor/components/diagram/input/inputNodeModel" {
     import { DefaultNodeModel } from "babylonjs-node-editor/components/diagram/defaultNodeModel";
-    import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
     import { GlobalState } from "babylonjs-node-editor/globalState";
+    import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+    import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
     /**
      * Generic node model which stores information about a node editor block
      */
     export class InputNodeModel extends DefaultNodeModel {
-        connection?: NodeMaterialConnectionPoint;
+        outputType: NodeMaterialBlockConnectionPointTypes;
+        readonly inputBlock: InputBlock;
         /**
          * Constructs the node model
          */
         constructor();
-        renderProperties(globalState: GlobalState): JSX.Element | null;
+        renderProperties(globalState: GlobalState): JSX.Element;
     }
 }
 declare module "babylonjs-node-editor/components/diagram/input/inputNodeWidget" {
@@ -852,7 +854,7 @@ declare module "babylonjs-node-editor/graphEditor" {
     }
     export class NodeCreationOptions {
         column: number;
-        nodeMaterialBlock?: NodeMaterialBlock;
+        nodeMaterialBlock: NodeMaterialBlock;
         type?: string;
         connection?: NodeMaterialConnectionPoint;
     }
@@ -1234,14 +1236,14 @@ declare module NODEEDITOR {
 }
 declare module NODEEDITOR {
     /**
-     * BABYLON.Texture node model which stores information about a node editor block
+     * Texture node model which stores information about a node editor block
      */
     export class TextureNodeModel extends DefaultNodeModel {
         private _block;
         /**
-         * BABYLON.Texture for the node if it exists
+         * Texture for the node if it exists
          */
-        texture: BABYLON.Nullable<BABYLON.Texture>;
+        texture: BABYLON.Nullable<BABYLON.BaseTexture>;
         /**
          * Constructs the node model
          */
@@ -1377,7 +1379,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IVector2PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Vector2PropertyTabComponent extends React.Component<IVector2PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -1417,7 +1419,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IVector3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Vector3PropertyTabComponent extends React.Component<IVector3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -1483,7 +1485,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IColor3PropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class Color3PropertyTabComponent extends React.Component<IColor3PropertyTabComponentProps> {
         render(): JSX.Element;
@@ -1518,7 +1520,7 @@ declare module NODEEDITOR {
 declare module NODEEDITOR {
     interface IFloatPropertyTabComponentProps {
         globalState: GlobalState;
-        connection: BABYLON.NodeMaterialConnectionPoint;
+        inputBlock: BABYLON.InputBlock;
     }
     export class FloatPropertyTabComponent extends React.Component<IFloatPropertyTabComponentProps> {
         render(): JSX.Element;
@@ -1550,12 +1552,13 @@ declare module NODEEDITOR {
      * Generic node model which stores information about a node editor block
      */
     export class InputNodeModel extends DefaultNodeModel {
-        connection?: BABYLON.NodeMaterialConnectionPoint;
+        outputType: BABYLON.NodeMaterialBlockConnectionPointTypes;
+        readonly inputBlock: BABYLON.InputBlock;
         /**
          * Constructs the node model
          */
         constructor();
-        renderProperties(globalState: GlobalState): JSX.Element | null;
+        renderProperties(globalState: GlobalState): JSX.Element;
     }
 }
 declare module NODEEDITOR {
@@ -1698,7 +1701,7 @@ declare module NODEEDITOR {
     }
     export class NodeCreationOptions {
         column: number;
-        nodeMaterialBlock?: BABYLON.NodeMaterialBlock;
+        nodeMaterialBlock: BABYLON.NodeMaterialBlock;
         type?: string;
         connection?: BABYLON.NodeMaterialConnectionPoint;
     }

+ 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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
     },
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7"
     },
     "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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -1 +1 @@
-{"engineOnly":252117,"sceneOnly":510292,"minGridMaterial":639202,"minStandardMaterial":765213}
+{"engineOnly":252171,"sceneOnly":510426,"minGridMaterial":639336,"minStandardMaterial":765405}

+ 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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7"
     },
     "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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7"
     },
     "engines": {
         "node": "*"

+ 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-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,8 +28,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.1.0-alpha.6",
-        "babylonjs-gltf2interface": "4.1.0-alpha.6"
+        "babylonjs": "4.1.0-alpha.7",
+        "babylonjs-gltf2interface": "4.1.0-alpha.7"
     },
     "engines": {
         "node": "*"

文件差異過大導致無法顯示
+ 1535 - 1626
dist/preview release/viewer/babylon.module.d.ts


文件差異過大導致無法顯示
+ 49 - 49
dist/preview release/viewer/babylon.viewer.js


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -22,6 +22,10 @@
 - Added support for sound sprites [Doc](https://doc.babylonjs.com/how_to/playing_sounds_and_music#playing-a-sound-sprite) ([Deltakosh](https://github.com/deltakosh/))
 - Display Oculus Quest controller when using a Quest in WebVR ([TrevorDev](https://github.com/TrevorDev))
 - Added startAndReleaseDragOnPointerEvents property to pointerDragBehavior which can be set to false for custom drag triggering ([TrevorDev](https://github.com/TrevorDev))
+- Effect renderer to render one or multiple shader effects to a texture ([TrevorDev](https://github.com/TrevorDev))
+- Added url parameters to web request modifiers ([PierreLeBlond](https://github.com/PierreLeBlond))
+- WebXR updated to spec as of June 27th ([TrevorDev](https://github.com/TrevorDev))
+- WebXR webVR parity helpers ([TrevorDev](https://github.com/TrevorDev))
 
 ### Engine
 - Morph targets now can morph UV channel as well ([Deltakosh](https://github.com/deltakosh/))
@@ -58,7 +62,7 @@
 
 ### Loaders
 - Added support for non-float accessors in animation data for glTF loader. ([bghgary](https://github.com/bghgary))
-- Support loading cube data in .basis loader ([TrevorDev](https://github.com/TrevorDev))
+- Support loading cube data in the .basis loader ([TrevorDev](https://github.com/TrevorDev))
 - Load glTF extras into BJS metadata ([pjoe](https://github.com/pjoe))
 
 ### Materials
@@ -68,6 +72,9 @@
 ### Sounds
 - Added `ISoundOptions.skipCodecCheck` to make `Sound` more flexible with URLs ([nbduke](https://github.com/nbduke))
 
+### Ray
+- Added `Ray.intersectsAxis` to translate screen to axis coordinates without checking collisions ([horusscope](https://github.com/horusscope))
+
 ### Documentation
 - Added a note on shallow bounding of getBoundingInfo ([tibotiber](https://github.com/tibotiber))
 
@@ -85,6 +92,9 @@
 - Fix bug when adding and removing observers in quick succession ([sable](https://github.com/thscott))
 - Cannon and Ammo forceUpdate will no longer cause an unexpected exception ([TrevorDev](https://github.com/TrevorDev))
 - Loading the same multi-material twice and disposing one should not impact the other ([TrevorDev](https://github.com/TrevorDev))
+- GLTF loader should now preserve the texture naming ([Drigax](https://github.com/Drigax))
+- GLTF exporter should no longer duplicate exported texture data ([Drigax](https://github.com/Drigax))
+- Avoid exception when disposing of Ammo cloth physics ([TrevorDev](https://github.com/TrevorDev))
 
 ## Breaking changes
 - Setting mesh.scaling to a new vector will no longer automatically call forceUpdate (this should be done manually when needed) ([TrevorDev](https://github.com/TrevorDev))

+ 6 - 6
inspector/src/inspector.ts

@@ -48,27 +48,27 @@ export class Inspector {
     private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
         for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
             var styleSheet: any = sourceDoc.styleSheets[index];
-            try{
+            try {
                 if (styleSheet.cssRules) { // for <style> elements
                     const newStyleEl = sourceDoc.createElement('style');
-    
+
                     for (var cssRule of styleSheet.cssRules) {
                         // write the text of each rule into the body of the style element
                         newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
                     }
-    
+
                     targetDoc.head!.appendChild(newStyleEl);
                 } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
                     const newLinkEl = sourceDoc.createElement('link');
-    
+
                     newLinkEl.rel = 'stylesheet';
                     newLinkEl.href = styleSheet.href;
                     targetDoc.head!.appendChild(newLinkEl);
                 }
-            }catch(e){
+            } catch (e) {
                 console.log(e)
             }
-            
+
         }
     }
 

+ 0 - 2
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -73,14 +73,12 @@ export class KHR_materials_pbrSpecularGlossiness implements IGLTFLoaderExtension
 
         if (properties.diffuseTexture) {
             promises.push(this._loader.loadTextureInfoAsync(`${context}/diffuseTexture`, properties.diffuseTexture, (texture) => {
-                texture.name = `${babylonMaterial.name} (Diffuse)`;
                 babylonMaterial.albedoTexture = texture;
             }));
         }
 
         if (properties.specularGlossinessTexture) {
             promises.push(this._loader.loadTextureInfoAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, (texture) => {
-                texture.name = `${babylonMaterial.name} (Specular Glossiness)`;
                 babylonMaterial.reflectivityTexture = texture;
             }));
 

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

@@ -58,7 +58,6 @@ export class KHR_materials_unlit implements IGLTFLoaderExtension {
 
             if (properties.baseColorTexture) {
                 promises.push(this._loader.loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, (texture) => {
-                    texture.name = `${babylonMaterial.name} (Base Color)`;
                     babylonMaterial.albedoTexture = texture;
                 }));
             }

+ 4 - 5
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1555,14 +1555,12 @@ export class GLTFLoader implements IGLTFLoader {
 
             if (properties.baseColorTexture) {
                 promises.push(this.loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, (texture) => {
-                    texture.name = `${babylonMaterial.name} (Base Color)`;
                     babylonMaterial.albedoTexture = texture;
                 }));
             }
 
             if (properties.metallicRoughnessTexture) {
                 promises.push(this.loadTextureInfoAsync(`${context}/metallicRoughnessTexture`, properties.metallicRoughnessTexture, (texture) => {
-                    texture.name = `${babylonMaterial.name} (Metallic Roughness)`;
                     babylonMaterial.metallicTexture = texture;
                 }));
 
@@ -1699,7 +1697,6 @@ export class GLTFLoader implements IGLTFLoader {
 
         if (material.normalTexture) {
             promises.push(this.loadTextureInfoAsync(`${context}/normalTexture`, material.normalTexture, (texture) => {
-                texture.name = `${babylonMaterial.name} (Normal)`;
                 babylonMaterial.bumpTexture = texture;
             }));
 
@@ -1714,7 +1711,6 @@ export class GLTFLoader implements IGLTFLoader {
 
         if (material.occlusionTexture) {
             promises.push(this.loadTextureInfoAsync(`${context}/occlusionTexture`, material.occlusionTexture, (texture) => {
-                texture.name = `${babylonMaterial.name} (Occlusion)`;
                 babylonMaterial.ambientTexture = texture;
             }));
 
@@ -1726,7 +1722,6 @@ export class GLTFLoader implements IGLTFLoader {
 
         if (material.emissiveTexture) {
             promises.push(this.loadTextureInfoAsync(`${context}/emissiveTexture`, material.emissiveTexture, (texture) => {
-                texture.name = `${babylonMaterial.name} (Emissive)`;
                 babylonMaterial.emissiveTexture = texture;
             }));
         }
@@ -1792,6 +1787,10 @@ export class GLTFLoader implements IGLTFLoader {
         const texture = ArrayItem.Get(`${context}/index`, this._gltf.textures, textureInfo.index);
         const promise = this._loadTextureAsync(`/textures/${textureInfo.index}`, texture, (babylonTexture) => {
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
+            if (texture.name)
+            {
+                babylonTexture.name = texture.name;
+            }
 
             GLTFLoader.AddPointerMetadata(babylonTexture, context);
             this._parent.onTextureLoadedObservable.notifyObservers(babylonTexture);

文件差異過大導致無法顯示
+ 746 - 1186
localDev/src/webgl-debug.js


+ 1 - 33
nodeEditor/src/components/diagram/defaultNodeModel.ts

@@ -1,7 +1,6 @@
 import { NodeModel, DiagramModel } from "storm-react-diagrams";
 import { Nullable } from 'babylonjs/types';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
-import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
 import { GraphEditor, NodeCreationOptions } from '../../graphEditor';
 import { GlobalState } from '../../globalState';
 import { DefaultPortModel } from './defaultPortModel';
@@ -52,7 +51,7 @@ export class DefaultNodeModel extends NodeModel {
                 var connectedNode;
                 var existingNodes = nodes.filter((n) => { return n.block === (connection as any)._connectedPoint._ownerBlock });
                 if (existingNodes.length == 0) {
-                    connectedNode = graphEditor.createNodeFromObject({ column: options.column + 1, nodeMaterialBlock: connection.connectedPoint._ownerBlock });
+                    connectedNode = graphEditor.createNodeFromObject({ nodeMaterialBlock: connection.connectedPoint._ownerBlock });
                 } else {
                     connectedNode = existingNodes[0];
                 }
@@ -63,37 +62,6 @@ export class DefaultNodeModel extends NodeModel {
                 } else {
                     model.addAll(link);
                 }
-            } else if (!connection.isUndefined) {
-                // Create value node for the connection
-                var type = ""
-                if (connection.type == NodeMaterialBlockConnectionPointTypes.Texture) {
-                    type = "Texture"
-                } else if (connection.type == NodeMaterialBlockConnectionPointTypes.Matrix) {
-                    type = "Matrix"
-                } else if (connection.type & NodeMaterialBlockConnectionPointTypes.Vector3OrColor3) {
-                    type = "Vector3"
-                } else if (connection.type & NodeMaterialBlockConnectionPointTypes.Vector2) {
-                    type = "Vector2"
-                } else if (connection.type & NodeMaterialBlockConnectionPointTypes.Vector3OrColor3OrVector4OrColor4) {
-                    type = "Vector4"
-                }
-                else if (connection.type & NodeMaterialBlockConnectionPointTypes.Float) {
-                    type = "Float"
-                }
-
-                // Create links
-                var localNode = graphEditor.addValueNode(type, options.column + 1, connection);
-                if (localNode) {
-                    var ports = localNode.getPorts()
-                    for (var key in ports) {
-                        let link = (ports[key] as DefaultPortModel).link(inputPort);
-                        if (graphEditor._toAdd) {
-                            graphEditor._toAdd.push(link);
-                        } else {
-                            model.addAll(link);
-                        }
-                    }
-                }
             }
         });
     }

+ 71 - 19
nodeEditor/src/components/diagram/diagram.scss

@@ -1,15 +1,46 @@
+.srd-node {
+    width: 200px;
+}
+
+.srd-node--selected > * {
+    border: 4px solid  rgb(0, 192, 255) !important;
+    border-radius: 20px;
+}
+          
+.srd-port {
+    background: rgb(0, 192, 255);
+    border-radius: 10px;
+    border: black 4px solid;
+
+    &.connected {
+        background: #CAB422;
+    }
+}
+
 .diagramBlock {
     background: white;
     width: 100%;
     border: 4px solid black;
+    border-radius: 20px;
+    display: grid;
+    grid-template-rows: 30px auto;
+    grid-template-columns: 50% 50%;
 
     &.input {
-        background: green;
+        background: #40866E;
         color:white;
+
+        .value {
+            grid-row: 2;
+        }
+
+        .outputs {
+            transform: translateY(5px);
+        }
     }
 
     &.attribute {
-        background: orange;
+        background: #40866E;
     }
 
     &.output {
@@ -21,31 +52,51 @@
         }
     }
 
-    .header {
-        margin: 10px;
+    .header {            
+        grid-row: 1;
+        grid-column: 1 / span 2;
+        border: 4px solid black;
+        border-top-right-radius: 16px;
+        border-top-left-radius: 16px;
         font-size: 16px;
         text-align: center;
+        margin: -1px;
         
         white-space: nowrap;
         text-overflow: ellipsis;
         overflow: hidden;
+        background: black;
+        color: white;
     }
 
     .value {
+        grid-row: 3;
+        grid-column: 1 / span 2;
         height: 34px;
         text-align: center;
-        font-size: 20px;
+        font-size: 18px;
         font-weight: bold;
+        margin: 0 10px;
 
-        .fullColor {
-            height: 100%;
+        .value-text {
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            overflow: hidden;
         }
     }
 
+    .preview {
+        border-bottom-left-radius: 16px;
+        border-bottom-right-radius: 16px;
+        padding-top: 2px;
+    }
+
     .inputs {
+        grid-row: 2;
+        grid-column: 1;
         .input-port {
             display: grid;
-            grid-template-columns: 4px calc(100% - 4px);
+            grid-template-columns: 10px calc(100% - 10px);
             grid-template-rows: 100%;
 
             .input-port-plug {
@@ -54,25 +105,27 @@
                 display: grid;
                 align-content: center;
                 margin-left: -11px;
-                
-                .srd-port {
-                    background: grey;
-                }
+
             }
 
             .input-port-label {
                 margin-left: 10px;
                 grid-column: 2;
                 grid-row: 1;         
-                margin-bottom: 2px;       
+                margin-bottom: 2px;    
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;   
             }
         }
     }
 
     .outputs {
+        grid-row: 2;
+        grid-column: 2;
         .output-port {
             display: grid;
-            grid-template-columns: calc(100% - 4px) 4px;
+            grid-template-columns: calc(100% - 10px) 10px;
             grid-template-rows: 100%;
 
             .output-port-plug {
@@ -80,10 +133,6 @@
                 grid-row: 1;
                 display: grid;
                 align-content: center;
-
-                .srd-port {
-                    background: grey;
-                }
             }
 
             .output-port-label {
@@ -91,7 +140,10 @@
                 margin-right: 10px;
                 grid-column: 1;
                 grid-row: 1;                        
-                margin-bottom: 2px;        
+                margin-bottom: 2px;   
+                overflow: hidden;
+                white-space: nowrap;
+                text-overflow: ellipsis;   
             }
         }
     }

+ 8 - 37
nodeEditor/src/components/diagram/generic/genericNodeWidget.tsx

@@ -1,9 +1,8 @@
 import * as React from "react";
-import { PortWidget } from "storm-react-diagrams";
-import { DefaultPortModel } from '../defaultPortModel';
 import { Nullable } from 'babylonjs/types';
 import { GlobalState } from '../../../globalState';
 import { GenericNodeModel } from './genericNodeModel';
+import { PortHelper } from '../portHelper';
 
 /**
  * GenericNodeWidgetProps
@@ -43,44 +42,16 @@ export class GenericNodeWidget extends React.Component<GenericNodeWidgetProps, G
     }
 
     render() {
+        // Header label
         var header = "";
-        var inputPorts = new Array<JSX.Element>()
-        var outputPorts = new Array<JSX.Element>()
-        if (this.props.node) {
-            // Header label
-            if (this.props.node.block) {
-                header = this.props.node.block.name;
-            }
-
-            // Input/Output ports
-            for (var key in this.props.node.ports) {
-                var port = this.props.node.ports[key] as DefaultPortModel;
-                if (port.position === "input") {
-                    inputPorts.push(
-                        <div key={key} className="input-port">
-                            <div className="input-port-plug">
-                                <PortWidget key={key} name={port.name} node={this.props.node} />
-                            </div>
-                            <div className="input-port-label">
-                                {port.name}
-                            </div>
-                        </div>
-                    )
-                } else {
-                    outputPorts.push(
-                        <div key={key} className="output-port">
-                            <div className="output-port-label">
-                                {port.name}
-                            </div>
-                            <div className="output-port-plug">
-                                <PortWidget key={key} name={port.name} node={this.props.node} />
-                            </div>
-                        </div>
-                    )
-                }
-            }
+        if (this.props.node && this.props.node.block) {
+            header = this.props.node.block.name;
         }
 
+        // Input/Output ports
+        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
+        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
+
         return (
             <div className={"diagramBlock" + (outputPorts.length === 0 ? " output" : "")}>
                 <div className="header">

+ 5 - 6
nodeEditor/src/components/diagram/input/inputNodeModel.tsx

@@ -1,14 +1,17 @@
 import * as React from "react";
 import { DefaultNodeModel } from '../defaultNodeModel';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
 import { GlobalState } from '../../../globalState';
 import { InputPropertyTabComponentProps } from './inputNodePropertyComponent';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
 /**
  * Generic node model which stores information about a node editor block
  */
 export class InputNodeModel extends DefaultNodeModel {
-    public connection?: NodeMaterialConnectionPoint;
+
+    public get inputBlock(): InputBlock {
+        return this.block as InputBlock;
+    }
 
 	/**
 	 * Constructs the node model
@@ -18,10 +21,6 @@ export class InputNodeModel extends DefaultNodeModel {
     }
 
     renderProperties(globalState: GlobalState) {
-        if (!this.connection) {
-            return null;
-        }
-
         return (
             <InputPropertyTabComponentProps globalState={globalState} inputNode={this} />
         );

+ 26 - 41
nodeEditor/src/components/diagram/input/inputNodePropertyComponent.tsx

@@ -8,7 +8,6 @@ import { InputNodeModel } from './inputNodeModel';
 import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
 import { OptionsLineComponent } from '../../../sharedComponents/optionsLineComponent';
 import { NodeMaterialWellKnownValues } from 'babylonjs/Materials/Node/nodeMaterialWellKnownValues';
-import { Vector2, Vector3, Matrix } from 'babylonjs/Maths/math';
 import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
 import { Color3PropertyTabComponent } from '../../propertyTab/properties/color3PropertyTabComponent';
 import { FloatPropertyTabComponent } from '../../propertyTab/properties/floatPropertyTabComponent';
@@ -27,53 +26,39 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
     }
 
     renderValue(globalState: GlobalState) {
-        let connection = this.props.inputNode.connection!;
-        switch (connection.type) {
+        let inputBlock = this.props.inputNode.inputBlock;
+        switch (inputBlock.type) {
             case NodeMaterialBlockConnectionPointTypes.Float:
                 return (
-                    <FloatPropertyTabComponent globalState={globalState} connection={connection} />
+                    <FloatPropertyTabComponent globalState={globalState} inputBlock={inputBlock} />
                 );
             case NodeMaterialBlockConnectionPointTypes.Vector2:
                 return (
-                    <Vector2PropertyTabComponent globalState={globalState} connection={connection} />
+                    <Vector2PropertyTabComponent globalState={globalState} inputBlock={inputBlock} />
                 );
             case NodeMaterialBlockConnectionPointTypes.Color3:
             case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
             case NodeMaterialBlockConnectionPointTypes.Color4:
                 return (
-                    <Color3PropertyTabComponent globalState={globalState} connection={connection} />
+                    <Color3PropertyTabComponent globalState={globalState} inputBlock={inputBlock} />
                 );
             case NodeMaterialBlockConnectionPointTypes.Vector3:
             case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
                 return (
-                    <Vector3PropertyTabComponent globalState={globalState} connection={connection} />
+                    <Vector3PropertyTabComponent globalState={globalState} inputBlock={inputBlock} />
                 );
         }
+
         return null;
     }
 
     setDefaultValue() {
-        let connection = this.props.inputNode.connection!;
-        switch (connection.type) {
-            case NodeMaterialBlockConnectionPointTypes.Float:
-                connection.value = 0;
-                break;
-            case NodeMaterialBlockConnectionPointTypes.Vector2:
-                connection.value = Vector2.Zero();
-                break;
-            case NodeMaterialBlockConnectionPointTypes.Vector3:
-            case NodeMaterialBlockConnectionPointTypes.Color3:
-            case NodeMaterialBlockConnectionPointTypes.Vector3OrColor3:
-                connection.value = Vector3.Zero();
-                break;
-            case NodeMaterialBlockConnectionPointTypes.Matrix:
-                connection.value = Matrix.Identity();
-                break;
-        }
+        let inputBlock = this.props.inputNode.inputBlock;
+        inputBlock.setDefaultValue();
     }
 
     render() {
-        let connection = this.props.inputNode.connection!;
+        let inputBlock = this.props.inputNode.inputBlock;
 
         var wellKnownOptions = [
             { label: "World", value: NodeMaterialWellKnownValues.World },
@@ -83,7 +68,7 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
             { label: "ViewxProjection", value: NodeMaterialWellKnownValues.ViewProjection },
             { label: "Projection", value: NodeMaterialWellKnownValues.Projection },
             { label: "Camera position", value: NodeMaterialWellKnownValues.CameraPosition },
-            { label: "Automatic", value: NodeMaterialWellKnownValues.Automatic },
+            { label: "Fog color", value: NodeMaterialWellKnownValues.FogColor },
         ];
 
         var attributeOptions = [
@@ -98,48 +83,48 @@ export class InputPropertyTabComponentProps extends React.Component<IInputProper
         return (
             <div>
                 <LineContainerComponent title="GENERAL">
-                    <TextLineComponent label="Type" value={StringTools.GetBaseType(connection.type)} />
+                    <TextLineComponent label="Type" value={StringTools.GetBaseType(inputBlock.type)} />
                 </LineContainerComponent>
                 <LineContainerComponent title="PROPERTIES">
                     <CheckBoxLineComponent label="Is mesh attribute" onSelect={value => {
                         if (!value) {
-                            connection.isUniform = true;
+                            inputBlock.isUniform = true;
                             this.setDefaultValue();
                         } else {
-                            connection.isAttribute = true;
+                            inputBlock.isAttribute = true;
                         }
                         this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         this.forceUpdate();
-                    }} isSelected={() => connection!.isAttribute} />
+                    }} isSelected={() => inputBlock.isAttribute} />
                     {
-                        connection.isAttribute &&
-                        <OptionsLineComponent label="Attribute" valuesAreStrings={true} options={attributeOptions} target={connection} propertyName="name" onSelect={(value: any) => {
-                            connection.setAsAttribute(value);
+                        inputBlock.isAttribute &&
+                        <OptionsLineComponent label="Attribute" valuesAreStrings={true} options={attributeOptions} target={inputBlock} propertyName="name" onSelect={(value: any) => {
+                            inputBlock.setAsAttribute(value);
                             this.forceUpdate();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />
                     }
                     {
-                        connection.isUniform &&
+                        inputBlock.isUniform &&
                         <CheckBoxLineComponent label="Is well known value" onSelect={value => {
                             if (value) {
-                                connection!.setAsWellKnownValue(NodeMaterialWellKnownValues.World);
+                                inputBlock.setAsWellKnownValue(NodeMaterialWellKnownValues.World);
                             } else {
-                                connection!.setAsWellKnownValue(null);
+                                inputBlock.setAsWellKnownValue(null);
                                 this.setDefaultValue();
                             }
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                             this.forceUpdate();
-                        }} isSelected={() => connection!.isWellKnownValue} />
+                        }} isSelected={() => inputBlock.isWellKnownValue} />
                     }
                     {
-                        connection.isUniform && !connection.isWellKnownValue &&
+                        inputBlock.isUniform && !inputBlock.isWellKnownValue &&
                         this.renderValue(this.props.globalState)
                     }
                     {
-                        connection.isUniform && connection.isWellKnownValue &&
-                        <OptionsLineComponent label="Well known value" options={wellKnownOptions} target={connection} propertyName="wellKnownValue" onSelect={(value: any) => {
-                            connection.setAsWellKnownValue(value);
+                        inputBlock.isUniform && inputBlock.isWellKnownValue &&
+                        <OptionsLineComponent label="Well known value" options={wellKnownOptions} target={inputBlock} propertyName="wellKnownValue" onSelect={(value: any) => {
+                            inputBlock.setAsWellKnownValue(value);
                             this.forceUpdate();
                             this.props.globalState.onRebuildRequiredObservable.notifyObservers();
                         }} />

+ 28 - 49
nodeEditor/src/components/diagram/input/inputNodeWidget.tsx

@@ -1,13 +1,12 @@
 import * as React from "react";
-import { PortWidget } from "storm-react-diagrams";
 import { InputNodeModel } from './inputNodeModel';
 import { Nullable } from 'babylonjs/types';
 import { GlobalState } from '../../../globalState';
-import { DefaultPortModel } from '../defaultPortModel';
 import { NodeMaterialWellKnownValues } from 'babylonjs/Materials/Node/nodeMaterialWellKnownValues';
 import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
 import { Color3 } from 'babylonjs/Maths/math';
 import { StringTools } from '../../../stringTools';
+import { PortHelper } from '../portHelper';
 
 /**
  * GenericNodeWidgetProps
@@ -42,62 +41,28 @@ export class InputNodeWidget extends React.Component<InputNodeWidgetProps> {
     renderValue(value: string) {
         if (value) {
             return (
-                <div>
+                <div className="value-text">
                     {value}
                 </div>
             )
         }
 
-        let connection = this.props.node!.connection;
-        if (!connection || !connection.isUniform) {
-            return null;
-        }
-
-        switch (connection.type) {
-            case NodeMaterialBlockConnectionPointTypes.Color3:
-            case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
-            case NodeMaterialBlockConnectionPointTypes.Color4: {
-                let color = connection.value as Color3;
-                return (
-                    <div className="fullColor" style={{ background: color.toHexString() }}></div>
-                )
-            }
-        }
-
         return null;
     }
 
     render() {
-        var outputPorts = new Array<JSX.Element>()
-        let port: DefaultPortModel;
-        if (this.props.node) {
-            for (var key in this.props.node.ports) {
-                port = this.props.node.ports[key] as DefaultPortModel;
-
-                outputPorts.push(
-                    <div key={key} className="output-port">
-                        <div className="output-port-label">
-                        </div>
-                        <div className="output-port-plug">
-                            <PortWidget key={key} name={port.name} node={this.props.node} />
-                        </div>
-                    </div>
-                );
-                break;
-            }
-        }
+        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, true);
 
-        let connection = this.props.node!.connection;
+        let inputBlock = this.props.node!.inputBlock;
         let value = "";
-        let name = "";
+        let name = StringTools.GetBaseType(inputBlock.output.type);
+        let color = "";
 
-        if (connection) {
-            name = StringTools.GetBaseType(connection.type)
-
-            if (connection.isAttribute) {
-                value = "mesh." + connection.name;
-            } else if (connection.isWellKnownValue) {
-                switch (connection.wellKnownValue) {
+        if (inputBlock) {
+            if (inputBlock.isAttribute) {
+                value = "mesh." + inputBlock.name;
+            } else if (inputBlock.isWellKnownValue) {
+                switch (inputBlock.wellKnownValue) {
                     case NodeMaterialWellKnownValues.World:
                         value = "World";
                         break;
@@ -119,17 +84,31 @@ export class InputNodeWidget extends React.Component<InputNodeWidgetProps> {
                     case NodeMaterialWellKnownValues.CameraPosition:
                         value = "Camera position";
                         break;
-                    case NodeMaterialWellKnownValues.Automatic:
-                        value = "Automatic";
+                    case NodeMaterialWellKnownValues.FogColor:
+                        value = "Fog color";
                         break;
                 }
+            } else {
+                if (!inputBlock || !inputBlock.isUniform) {
+                    return null;
+                }
+
+                switch (inputBlock.type) {
+                    case NodeMaterialBlockConnectionPointTypes.Color3:
+                    case NodeMaterialBlockConnectionPointTypes.Color3OrColor4:
+                    case NodeMaterialBlockConnectionPointTypes.Color4: {
+                        color = (inputBlock.value as Color3).toHexString();
+                    }
+                }
             }
         } else {
             name = "Not connected input";
         }
 
         return (
-            <div className={"diagramBlock input" + (connection && connection.isAttribute ? " attribute" : "")}>
+            <div className={"diagramBlock input" + (inputBlock && inputBlock.isAttribute ? " attribute" : "")} style={{
+                background: color
+            }}>
                 <div className="header">
                     {name}
                 </div>

+ 2 - 2
nodeEditor/src/components/diagram/light/lightNodeModel.tsx

@@ -18,11 +18,11 @@ export class LightNodeModel extends DefaultNodeModel {
 	 * Light for the node if it exists
 	 */
     public get light(): Nullable<Light> {
-        return this._block.light.value;
+        return this._block.light;
     }
 
     public set light(value: Nullable<Light>) {
-        this._block.light.value = value;
+        this._block.light = value;
     }
 
 	/**

+ 4 - 35
nodeEditor/src/components/diagram/light/lightNodeWidget.tsx

@@ -1,9 +1,8 @@
 import * as React from "react";
-import { PortWidget } from "storm-react-diagrams";
 import { LightNodeModel } from './lightNodeModel';
 import { Nullable } from 'babylonjs/types';
 import { GlobalState } from '../../../globalState';
-import { DefaultPortModel } from '../defaultPortModel';
+import { PortHelper } from '../portHelper';
 
 /**
  * GenericNodeWidgetProps
@@ -36,39 +35,9 @@ export class LightNodeWidget extends React.Component<ILightNodeWidgetProps> {
     }
 
     render() {
-        var inputPorts = new Array<JSX.Element>();
-        var outputPorts = new Array<JSX.Element>();
-        if (this.props.node) {
-            // Input/Output ports
-            for (var key in this.props.node.ports) {
-                var port = this.props.node.ports[key] as DefaultPortModel;
-                if (port.position === "input") {
-                    if (port.name !== "light") {
-                        inputPorts.push(
-                            <div key={key} className="input-port">
-                                <div className="input-port-plug">
-                                    <PortWidget key={key} name={port.name} node={this.props.node} />
-                                </div>
-                                <div className="input-port-label">
-                                    {port.name}
-                                </div>
-                            </div>
-                        )
-                    }
-                } else {
-                    outputPorts.push(
-                        <div key={key} className="output-port">
-                            <div className="output-port-label">
-                                {port.name}
-                            </div>
-                            <div className="output-port-plug">
-                                <PortWidget key={key} name={port.name} node={this.props.node} />
-                            </div>
-                        </div>
-                    )
-                }
-            }
-        }
+        // Input/Output ports
+        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
+        var inputPorts = PortHelper.GenerateInputPorts(this.props.node);
 
         return (
             <div className={"diagramBlock"}>

+ 61 - 0
nodeEditor/src/components/diagram/portHelper.tsx

@@ -0,0 +1,61 @@
+import * as React from "react";
+import { PortWidget } from "storm-react-diagrams";
+import { DefaultNodeModel } from './defaultNodeModel';
+import { DefaultPortModel } from './defaultPortModel';
+import { Nullable } from 'babylonjs/types';
+
+
+export class PortHelper {
+    public static GenerateOutputPorts(node: Nullable<DefaultNodeModel>, ignoreLabel: boolean) {
+        if (!node) {
+            return new Array<JSX.Element>();
+        }
+        let outputPorts = [];
+        for (var key in node.ports) {
+            let port = node.ports[key] as DefaultPortModel;
+            if (port.position === "output") {
+                outputPorts.push(
+                    <div key={key} className="output-port">
+                        {
+                            !ignoreLabel &&
+                            <div className="output-port-label">
+                                {port.name}
+                            </div>
+                        }
+                        <div className="output-port-plug">
+                            <PortWidget key={key} name={port.name} node={node} className={port.connection && port.connection.endpoints.length > 0 ? "connected" : ""} />
+                        </div>
+                    </div>
+                );
+            }
+        }
+
+        return outputPorts;
+    }
+
+    public static GenerateInputPorts(node: Nullable<DefaultNodeModel>, includeOnly?: string[]) {
+        if (!node) {
+            return new Array<JSX.Element>();
+        }
+        let inputPorts = [];
+        for (var key in node.ports) {
+            let port = node.ports[key] as DefaultPortModel;
+            if (port.position === "input") {
+                if (!includeOnly || includeOnly.indexOf(port.name) !== -1) {
+                    inputPorts.push(
+                        <div key={key} className="input-port">
+                            <div className="input-port-plug">
+                                <PortWidget key={key} name={port.name} node={node} className={port.connection && port.connection.connectedPoint ? "connected" : ""} />
+                            </div>
+                            <div className="input-port-label">
+                                {port.name}
+                            </div>
+                        </div>
+                    );
+                }
+            }
+        }
+
+        return inputPorts;
+    }
+}

+ 6 - 6
nodeEditor/src/components/diagram/texture/textureNodeModel.tsx

@@ -1,12 +1,12 @@
 import * as React from 'react';
 import { Nullable } from 'babylonjs/types';
-import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 import { DefaultNodeModel } from '../defaultNodeModel';
 import { GlobalState } from '../../../globalState';
 import { TexturePropertyTabComponent } from './texturePropertyTabComponent';
 import { NodeCreationOptions, GraphEditor } from '../../../graphEditor';
 import { DiagramModel } from 'storm-react-diagrams/dist/@types/src/models/DiagramModel';
-import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/textureBlock';
+import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 
 /**
  * Texture node model which stores information about a node editor block
@@ -17,12 +17,12 @@ export class TextureNodeModel extends DefaultNodeModel {
 	/**
 	 * Texture for the node if it exists
 	 */
-    public get texture(): Nullable<Texture> {
-        return this._block.texture.value;
+    public get texture(): Nullable<BaseTexture> {
+        return this._block.texture;
     }
 
-    public set texture(value: Nullable<Texture>) {
-        this._block.texture.value = value;
+    public set texture(value: Nullable<BaseTexture>) {
+        this._block.texture = value;
     }
 
 	/**

+ 4 - 33
nodeEditor/src/components/diagram/texture/textureNodeWidget.tsx

@@ -1,10 +1,9 @@
 import * as React from "react";
-import { PortWidget } from "storm-react-diagrams";
 import { TextureNodeModel } from './textureNodeModel';
 import { TextureLineComponent } from "../../../sharedComponents/textureLineComponent"
 import { Nullable } from 'babylonjs/types';
 import { GlobalState } from '../../../globalState';
-import { DefaultPortModel } from '../defaultPortModel';
+import { PortHelper } from '../portHelper';
 
 /**
  * GenericNodeWidgetProps
@@ -37,37 +36,9 @@ export class TextureNodeWidget extends React.Component<ITextureNodeWidgetProps>
     }
 
     render() {
-        var inputPorts = new Array<JSX.Element>();
-        var outputPorts = new Array<JSX.Element>();
-        if (this.props.node) {
-            // Input/Output ports
-            for (var key in this.props.node.ports) {
-                var port = this.props.node.ports[key] as DefaultPortModel;
-                if (port.position === "output") {
-                    outputPorts.push(
-                        <div key={key} className="output-port">
-                            <div className="output-port-label">
-                                {port.name}
-                            </div>
-                            <div className="output-port-plug">
-                                <PortWidget key={key} name={port.name} node={this.props.node} />
-                            </div>
-                        </div>
-                    );
-                } else if (port.name === "uv") {
-                    inputPorts.push(
-                        <div key={key} className="input-port">
-                            <div className="input-port-plug">
-                                <PortWidget key={key} name={port.name} node={this.props.node} />
-                            </div>
-                            <div className="input-port-label">
-                                {port.name}
-                            </div>
-                        </div>
-                    )
-                }
-            }
-        }
+        // Input/Output ports
+        var outputPorts = PortHelper.GenerateOutputPorts(this.props.node, false);
+        var inputPorts = PortHelper.GenerateInputPorts(this.props.node, ["uv"]);
 
         return (
             <div className={"diagramBlock"}>

+ 3 - 2
nodeEditor/src/components/diagram/texture/texturePropertyTabComponent.tsx

@@ -1,7 +1,7 @@
 
 import * as React from "react";
 import { GlobalState } from '../../../globalState';
-import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 import { FileButtonLineComponent } from '../../../sharedComponents/fileButtonLineComponent';
 import { Tools } from 'babylonjs/Misc/tools';
 import { Engine } from 'babylonjs/Engines/engine';
@@ -10,6 +10,7 @@ import { TextLineComponent } from '../../../sharedComponents/textLineComponent';
 import { LineContainerComponent } from '../../../sharedComponents/lineContainerComponent';
 import { TextInputLineComponent } from '../../../sharedComponents/textInputLineComponent';
 import { CheckBoxLineComponent } from '../../../sharedComponents/checkBoxLineComponent';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
 
 interface ITexturePropertyTabComponentProps {
     globalState: GlobalState;
@@ -27,7 +28,7 @@ export class TexturePropertyTabComponent extends React.Component<ITexturePropert
             return;
         }
 
-        let texture = this.props.node.texture as Texture;
+        let texture = this.props.node.texture as BaseTexture;
         if (!texture) {
             this.props.node.texture = new Texture(null, Engine.LastCreatedScene)
             texture = this.props.node.texture;

+ 18 - 0
nodeEditor/src/components/nodeList/nodeList.scss

@@ -22,6 +22,24 @@
         .underline {
             border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
         }
+
+        .draggableLine {
+            height: 30px;
+            display: grid;
+            align-items: center;
+            justify-items: stretch;
+            background: #222222;
+            border: 1px solid rgb(51, 122, 183) !important;
+            text-align: center;
+            margin: 4px 20px;
+            box-sizing: border-box;
+            border-radius: 10px;
+
+            &:hover {
+                background: rgb(51, 122, 183);
+                color: white;
+            }
+        }
     
         .buttonLine {
             height: 30px;

+ 8 - 37
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -2,59 +2,30 @@
 import * as React from "react";
 import { GlobalState } from '../../globalState';
 import { LineContainerComponent } from '../../sharedComponents/lineContainerComponent';
-import { ButtonLineComponent } from '../../sharedComponents/buttonLineComponent';
-import { AlphaTestBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/alphaTestBlock';
-import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
-import { ImageProcessingBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/imageProcessingBlock';
-import { RGBAMergerBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaMergerBlock';
-import { RGBASplitterBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaSplitterBlock';
-import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/textureBlock';
-import { BonesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/bonesBlock';
-import { InstancesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/instancesBlock';
-import { MorphTargetsBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/morphTargetsBlock';
-import { VertexOutputBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/vertexOutputBlock';
-import { FogBlock } from 'babylonjs/Materials/Node/Blocks/Dual/fogBlock';
-import { AddBlock } from 'babylonjs/Materials/Node/Blocks/addBlock';
-import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
-import { MatrixMultiplicationBlock } from 'babylonjs/Materials/Node/Blocks/matrixMultiplicationBlock';
-import { MultiplyBlock } from 'babylonjs/Materials/Node/Blocks/multiplyBlock';
-import { Vector2TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector2TransformBlock';
-import { Vector3TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector3TransformBlock';
-import { Vector4TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector4TransformBlock';
-import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
-import { ScaleBlock } from 'babylonjs/Materials/Node/Blocks/scaleBlock';
-import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
+import { DraggableLineComponent } from '../../sharedComponents/draggableLineComponent';
 
 require("./nodeList.scss");
 
 interface INodeListComponentProps {
     globalState: GlobalState;
-    onAddValueNode: (b: string) => void;
-    onAddNodeFromClass: (ObjectClass: typeof NodeMaterialBlock) => void;
 }
 
 export class NodeListComponent extends React.Component<INodeListComponentProps> {
     render() {
         // Block types used to create the menu from
         const allBlocks = {
-            Vertex: [BonesBlock, InstancesBlock, MorphTargetsBlock],
-            Fragment: [AlphaTestBlock, , ImageProcessingBlock, RGBAMergerBlock, RGBASplitterBlock, TextureBlock, LightBlock],
-            Outputs: [VertexOutputBlock, FragmentOutputBlock],
-            Dual: [FogBlock],
-            Math: [AddBlock, ClampBlock, MatrixMultiplicationBlock, MultiplyBlock, ScaleBlock, Vector2TransformBlock, Vector3TransformBlock, Vector4TransformBlock],
-            Inputs: ["Vector2", "Vector3", "Vector4", "Color3", "Color4", "Matrix"],
+            Vertex: ["BonesBlock", "InstancesBlock", "MorphTargetsBlock"],
+            Fragment: ["AlphaTestBlock", "ImageProcessingBlock", "RGBAMergerBlock", "RGBASplitterBlock", "TextureBlock", "LightBlock", "FogBlock"],
+            Outputs: ["VertexOutputBlock", "FragmentOutputBlock"],
+            Math: ["AddBlock", "ClampBlock", "MultiplyBlock", "Vector2TransformBlock", "Vector3TransformBlock", "Vector4TransformBlock"],
+            Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "Matrix"],
         }
 
         // Create node menu
         var blockMenu = []
         for (var key in allBlocks) {
-            var blockList = (allBlocks as any)[key].map((b: any) => {
-                var label = typeof b === "string" ? b : b.prototype.getClassName().replace("Block", "")
-                var onClick = typeof b === "string" ? () => {
-                    this.props.onAddValueNode(b);
-                    this.props.globalState.onUpdateRequiredObservable.notifyObservers();
-                } : () => { this.props.onAddNodeFromClass(b) };
-                return <ButtonLineComponent key={label} label={label} onClick={onClick} />
+            var blockList = (allBlocks as any)[key].map((block: any, i: number) => {
+                return <DraggableLineComponent key={block} data={block} />
             })
             blockMenu.push(
                 <LineContainerComponent key={key + " blocks"} title={key + " blocks"} closed={false}>

+ 3 - 3
nodeEditor/src/components/propertyTab/properties/color3PropertyTabComponent.tsx

@@ -1,19 +1,19 @@
 
 import * as React from "react";
 import { GlobalState } from '../../../globalState';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
 import { Color3LineComponent } from '../../../sharedComponents/color3LineComponent';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
 interface IColor3PropertyTabComponentProps {
     globalState: GlobalState;
-    connection: NodeMaterialConnectionPoint;
+    inputBlock: InputBlock;
 }
 
 export class Color3PropertyTabComponent extends React.Component<IColor3PropertyTabComponentProps> {
 
     render() {
         return (
-            <Color3LineComponent label="Value" target={this.props.connection} propertyName="value" onChange={() => {
+            <Color3LineComponent label="Value" target={this.props.inputBlock} propertyName="value" onChange={() => {
                 this.props.globalState.onUpdateRequiredObservable.notifyObservers();
             }}></Color3LineComponent>
         );

+ 3 - 3
nodeEditor/src/components/propertyTab/properties/floatPropertyTabComponent.tsx

@@ -1,19 +1,19 @@
 
 import * as React from "react";
 import { GlobalState } from '../../../globalState';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
 import { FloatLineComponent } from '../../../sharedComponents/floatLineComponent';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
 interface IFloatPropertyTabComponentProps {
     globalState: GlobalState;
-    connection: NodeMaterialConnectionPoint;
+    inputBlock: InputBlock;
 }
 
 export class FloatPropertyTabComponent extends React.Component<IFloatPropertyTabComponentProps> {
 
     render() {
         return (
-            <FloatLineComponent label="Value" target={this.props.connection} propertyName="value"></FloatLineComponent>
+            <FloatLineComponent label="Value" target={this.props.inputBlock} propertyName="value"></FloatLineComponent>
         );
     }
 }

+ 3 - 3
nodeEditor/src/components/propertyTab/properties/vector2PropertyTabComponent.tsx

@@ -2,18 +2,18 @@
 import * as React from "react";
 import { GlobalState } from '../../../globalState';
 import { Vector2LineComponent } from '../../../sharedComponents/vector2LineComponent';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
 interface IVector2PropertyTabComponentProps {
     globalState: GlobalState;
-    connection: NodeMaterialConnectionPoint;
+    inputBlock: InputBlock;
 }
 
 export class Vector2PropertyTabComponent extends React.Component<IVector2PropertyTabComponentProps> {
 
     render() {
         return (
-            <Vector2LineComponent label="Value" target={this.props.connection} propertyName="value"></Vector2LineComponent>
+            <Vector2LineComponent label="Value" target={this.props.inputBlock} propertyName="value"></Vector2LineComponent>
         );
     }
 }

+ 3 - 3
nodeEditor/src/components/propertyTab/properties/vector3PropertyTabComponent.tsx

@@ -2,18 +2,18 @@
 import * as React from "react";
 import { GlobalState } from '../../../globalState';
 import { Vector3LineComponent } from '../../../sharedComponents/vector3LineComponent';
-import { NodeMaterialConnectionPoint } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPoint';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 
 interface IVector3PropertyTabComponentProps {
     globalState: GlobalState;
-    connection: NodeMaterialConnectionPoint;
+    inputBlock: InputBlock;
 }
 
 export class Vector3PropertyTabComponent extends React.Component<IVector3PropertyTabComponentProps> {
 
     render() {
         return (
-            <Vector3LineComponent label="Value" target={this.props.connection} propertyName="value"></Vector3LineComponent>
+            <Vector3LineComponent label="Value" target={this.props.inputBlock} propertyName="value"></Vector3LineComponent>
         );
     }
 }

+ 3 - 0
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -61,6 +61,9 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         <ButtonLineComponent label="Zoom to fit" onClick={() => {
                             this.props.globalState.onZoomToFitRequiredObservable.notifyObservers();
                         }} />
+                        <ButtonLineComponent label="Reorganize" onClick={() => {
+                            this.props.globalState.onReOrganizedRequiredObservable.notifyObservers();
+                        }} />
                     </LineContainerComponent>
                 </div>
             </div>

+ 1 - 0
nodeEditor/src/globalState.ts

@@ -13,5 +13,6 @@ export class GlobalState {
     onResetRequiredObservable = new Observable<void>();
     onUpdateRequiredObservable = new Observable<void>();
     onZoomToFitRequiredObservable = new Observable<void>();
+    onReOrganizedRequiredObservable = new Observable<void>();
     onLogRequiredObservable = new Observable<LogEntry>();
 }

+ 245 - 93
nodeEditor/src/graphEditor.tsx

@@ -2,11 +2,11 @@ import {
     DiagramEngine,
     DiagramModel,
     DiagramWidget,
-    MoveCanvasAction,
     LinkModel
 } from "storm-react-diagrams";
 
 import * as React from "react";
+import * as dagre from "dagre";
 import { GlobalState } from './globalState';
 
 import { GenericNodeFactory } from './components/diagram/generic/genericNodeFactory';
@@ -22,13 +22,31 @@ import { TextureNodeModel } from './components/diagram/texture/textureNodeModel'
 import { DefaultPortModel } from './components/diagram/defaultPortModel';
 import { InputNodeFactory } from './components/diagram/input/inputNodeFactory';
 import { InputNodeModel } from './components/diagram/input/inputNodeModel';
-import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/textureBlock';
-import { Vector2, Vector3, Vector4, Matrix, Color3, Color4 } from 'babylonjs/Maths/math';
+import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 import { LogComponent, LogEntry } from './components/log/logComponent';
 import { LightBlock } from 'babylonjs/Materials/Node/Blocks/Dual/lightBlock';
 import { LightNodeModel } from './components/diagram/light/lightNodeModel';
 import { LightNodeFactory } from './components/diagram/light/lightNodeFactory';
 import { DataStorage } from './dataStorage';
+import { NodeMaterialBlockConnectionPointTypes } from 'babylonjs/Materials/Node/nodeMaterialBlockConnectionPointTypes';
+import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
+import { Nullable } from 'babylonjs/types';
+import { BonesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/bonesBlock';
+import { InstancesBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/instancesBlock';
+import { MorphTargetsBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/morphTargetsBlock';
+import { AlphaTestBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/alphaTestBlock';
+import { ImageProcessingBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/imageProcessingBlock';
+import { RGBAMergerBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaMergerBlock';
+import { RGBASplitterBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/rgbaSplitterBlock';
+import { FogBlock } from 'babylonjs/Materials/Node/Blocks/Dual/fogBlock';
+import { VertexOutputBlock } from 'babylonjs/Materials/Node/Blocks/Vertex/vertexOutputBlock';
+import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragmentOutputBlock';
+import { AddBlock } from 'babylonjs/Materials/Node/Blocks/addBlock';
+import { ClampBlock } from 'babylonjs/Materials/Node/Blocks/clampBlock';
+import { MultiplyBlock } from 'babylonjs/Materials/Node/Blocks/multiplyBlock';
+import { Vector2TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector2TransformBlock';
+import { Vector3TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector3TransformBlock';
+import { Vector4TransformBlock } from 'babylonjs/Materials/Node/Blocks/vector4TransformBlock';
 
 require("storm-react-diagrams/dist/style.min.css");
 require("./main.scss");
@@ -53,8 +71,7 @@ interface IGraphEditorProps {
 }
 
 export class NodeCreationOptions {
-    column: number;
-    nodeMaterialBlock?: NodeMaterialBlock;
+    nodeMaterialBlock: NodeMaterialBlock;
     type?: string;
     connection?: NodeMaterialConnectionPoint;
 }
@@ -69,49 +86,33 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
     public _toAdd: LinkModel[] | null = [];
 
     /**
-     * Current row/column position used when adding new nodes
-     */
-    private _rowPos = new Array<number>();
-
-    /**
      * Creates a node and recursivly creates its parent nodes from it's input
      * @param nodeMaterialBlock 
      */
     public createNodeFromObject(options: NodeCreationOptions) {
-        // Update rows/columns
-        if (this._rowPos[options.column] == undefined) {
-            this._rowPos[options.column] = 0;
-        } else {
-            this._rowPos[options.column]++;
-        }
-
         // Create new node in the graph
         var newNode: DefaultNodeModel;
         var filterInputs = [];
 
-        if (options.nodeMaterialBlock) {
-            if (options.nodeMaterialBlock instanceof TextureBlock) {
-                newNode = new TextureNodeModel();
-                filterInputs.push("uv");
-            } else if (options.nodeMaterialBlock instanceof LightBlock) {
-                newNode = new LightNodeModel();
-                filterInputs.push("worldPosition");
-                filterInputs.push("worldNormal");
-                filterInputs.push("cameraPosition");
-            } else {
-                newNode = new GenericNodeModel();
-            }
-
-            if (options.nodeMaterialBlock.isFinalMerger) {
-                this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
-            }
-
-        } else {
+        if (options.nodeMaterialBlock instanceof TextureBlock) {
+            newNode = new TextureNodeModel();
+            filterInputs.push("uv");
+        } else if (options.nodeMaterialBlock instanceof LightBlock) {
+            newNode = new LightNodeModel();
+            filterInputs.push("worldPosition");
+            filterInputs.push("worldNormal");
+            filterInputs.push("cameraPosition");
+        } else if (options.nodeMaterialBlock instanceof InputBlock) {
             newNode = new InputNodeModel();
-            (newNode as InputNodeModel).connection = options.connection;
+        } else {
+            newNode = new GenericNodeModel();
+        }
+
+        if (options.nodeMaterialBlock.isFinalMerger) {
+            this.props.globalState.nodeMaterial!.addOutputNode(options.nodeMaterialBlock);
         }
+
         this._nodes.push(newNode)
-        newNode.setPosition(1600 - (300 * options.column), 210 * this._rowPos[options.column])
         this._model.addAll(newNode);
 
         if (options.nodeMaterialBlock) {
@@ -155,7 +156,6 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         });
 
         this.props.globalState.onResetRequiredObservable.add(() => {
-            this._rowPos = [];
             this.build();
             if (this.props.globalState.nodeMaterial) {
                 this.buildMaterial();
@@ -170,9 +170,67 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this._engine.zoomToFit();
         });
 
+        this.props.globalState.onReOrganizedRequiredObservable.add(() => {
+            this.reOrganize();
+        })
+
         this.build();
     }
 
+    distributeGraph() {
+        let nodes = this.mapElements();
+        let edges = this.mapEdges();
+        let graph = new dagre.graphlib.Graph();
+        graph.setGraph({});
+        graph.setDefaultEdgeLabel(() => ({}));
+        graph.graph().rankdir = "LR";
+        //add elements to dagre graph
+        nodes.forEach(node => {
+            graph.setNode(node.id, node.metadata);
+        });
+        edges.forEach(edge => {
+            if (edge.from && edge.to) {
+                graph.setEdge(edge.from.id, edge.to.id);
+            }
+        });
+        //auto-distribute
+        dagre.layout(graph);
+        return graph.nodes().map(node => graph.node(node));
+    }
+
+    mapElements() {
+        let output = [];
+
+        // dagre compatible format
+        for (var nodeName in this._model.nodes) {
+            let node = this._model.nodes[nodeName];
+            let size = {
+                width: node.width,
+                height: node.height
+            };
+            output.push({ id: node.id, metadata: { ...size, id: node.id } });
+        }
+
+        return output;
+    }
+
+    mapEdges() {
+        // returns links which connects nodes
+        // we check are there both from and to nodes in the model. Sometimes links can be detached
+        let output = [];
+
+        for (var linkName in this._model.links) {
+            let link = this._model.links[linkName];
+
+            output.push({
+                from: link.sourcePort!.parent,
+                to: link.targetPort!.parent
+            });
+        }
+
+        return output;
+    }
+
     buildMaterial() {
         if (!this.props.globalState.nodeMaterial) {
             return;
@@ -201,6 +259,8 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                     if (targetBlock && targetBlock.isFinalMerger) {
                         this.props.globalState.nodeMaterial!.removeOutputNode(targetBlock);
                     }
+
+                    this.props.globalState.onSelectionChangedObservable.notifyObservers(null);
                 }
             },
             linksUpdated: (e) => {
@@ -215,16 +275,15 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                                 link.output.connection.disconnectFrom(link.input.connection)
                                 link.input.syncWithNodeMaterialConnectionPoint(link.input.connection)
                                 link.output.syncWithNodeMaterialConnectionPoint(link.output.connection)
-                            } else if (link.input.connection.value) {
-                                link.input.connection.value = null;
                             }
                         }
                     }
+                    this.forceUpdate();
+                    return;
                 }
 
                 e.link.addListener({
                     sourcePortChanged: () => {
-                        console.log("port change")
                     },
                     targetPortChanged: () => {
                         // Link is created with a target port
@@ -232,15 +291,18 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
 
                         if (link) {
                             if (link.output.connection && link.input.connection) {
-                                link.output.connection.connectTo(link.input.connection)
-                            } else if (link.input.connection) {
-                                if (!link.output.connection) { // Input Node
-                                    let name = link.output.name;
-                                    link.output.syncWithNodeMaterialConnectionPoint(link.input.connection);
-                                    link.output.name = name;
-                                    (link.output.getNode() as InputNodeModel).connection = link.output.connection!;
-                                    link.input.connection.value = link.output.defaultValue;
+                                // Disconnect previous connection
+                                for (var key in link.input.links) {
+                                    let other = link.input.links[key];
+
+                                    if (other.getSourcePort() !== link.output) {
+                                        other.remove();
+                                    }
                                 }
+
+                                link.output.connection.connectTo(link.input.connection);
+
+                                this.forceUpdate();
                             }
                             if (this.props.globalState.nodeMaterial) {
                                 this.buildMaterial();
@@ -255,10 +317,10 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         if (this.props.globalState.nodeMaterial) {
             var material: any = this.props.globalState.nodeMaterial;
             material._vertexOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject({ column: 0, nodeMaterialBlock: n });
+                this.createNodeFromObject({ nodeMaterialBlock: n });
             })
             material._fragmentOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject({ column: 0, nodeMaterialBlock: n });
+                this.createNodeFromObject({ nodeMaterialBlock: n });
             })
         }
 
@@ -269,55 +331,58 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             }
             this._toAdd = null;
             this._engine.setDiagramModel(this._model);
-            this.forceUpdate();
-        }, 550);
-    }
 
-    addNodeFromClass(ObjectClass: typeof NodeMaterialBlock) {
-        var block = new ObjectClass(ObjectClass.prototype.getClassName())
-        var localNode = this.createNodeFromObject({ column: 0, nodeMaterialBlock: block })
-        var widget = (this.refs["test"] as DiagramWidget);
-
-        this.forceUpdate();
+            this.forceUpdate();
 
-        // This is needed to fix link offsets when created, (eg. create a fog block)
-        // Todo figure out how to correct this without this
-        setTimeout(() => {
-            widget.startFiringAction(new MoveCanvasAction(1, 0, this._model));
+            this.reOrganize();
         }, 500);
-
-        return localNode;
     }
 
-    addValueNode(type: string, column = 0, connection?: NodeMaterialConnectionPoint) {
-        var localNode = this.createNodeFromObject({ column: column, type: type, connection: connection })
-        var outPort = new DefaultPortModel(type, "output");
-
-        localNode.addPort(outPort);
+    reOrganize() {
+        let nodes = this.distributeGraph();
+        nodes.forEach(node => {
+            for (var nodeName in this._model.nodes) {
+                let modelNode = this._model.nodes[nodeName];
 
-        if (!connection) {
-            switch (type) {
-                case "Vector2":
-                    outPort.defaultValue = Vector2.Zero();
-                    break;
-                case "Vector3":
-                    outPort.defaultValue = Vector3.Zero();
-                    break;
-                case "Vector4":
-                    outPort.defaultValue = Vector4.Zero();
-                    break;
-                case "Matrix":
-                    outPort.defaultValue = Matrix.Identity();
-                    break;
-                case "Color3":
-                    outPort.defaultValue = Color3.White();
-                    break;
-                case "Color4":
-                    outPort.defaultValue = new Color4(1, 1, 1, 1);
-                    break;
+                if (modelNode.id === node.id) {
+                    modelNode.setPosition(node.x - node.width / 2, node.y - node.height / 2);
+                    return;
+                }
             }
+        });
+        this.forceUpdate();
+    }
+
+    addValueNode(type: string) {
+        let nodeType: NodeMaterialBlockConnectionPointTypes = NodeMaterialBlockConnectionPointTypes.Vector3;
+        switch (type) {
+            case "Float":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Float;
+                break;
+            case "Vector2":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Vector2;
+                break;
+            case "Vector3":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Vector3;
+                break;
+            case "Vector4":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Vector4;
+                break;
+            case "Matrix":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Matrix;
+                break;
+            case "Color3":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Color3;
+                break;
+            case "Color4":
+                nodeType = NodeMaterialBlockConnectionPointTypes.Color4;
+                break;
         }
 
+        let newInputBlock = new InputBlock(type, undefined, nodeType);
+        newInputBlock.setDefaultValue();
+        var localNode = this.createNodeFromObject({ type: type, nodeMaterialBlock: newInputBlock })
+
         return localNode;
     }
 
@@ -366,6 +431,84 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
         return `${this._leftWidth}px 4px calc(100% - ${this._leftWidth + 8 + this._rightWidth}px) 4px ${this._rightWidth}px`;
     }
 
+    emitNewBlock(event: React.DragEvent<HTMLDivElement>) {
+        var data = event.dataTransfer.getData("babylonjs-material-node") as string;
+
+        if (data.indexOf("Block") === -1) {
+            this.addValueNode(data);
+        } else {
+            let block: Nullable<NodeMaterialBlock> = null;
+
+            switch (data) {
+                case "BonesBlock":
+                    block = new BonesBlock("Bones");
+                    break;
+                case "InstancesBlock":
+                    block = new InstancesBlock("Instances");
+                    break;
+                case "MorphTargetsBlock":
+                    block = new MorphTargetsBlock("MorphTargets");
+                    break;
+                case "AlphaTestBlock":
+                    block = new AlphaTestBlock("AlphaTest");
+                    break;
+                case "ImageProcessingBlock":
+                    block = new ImageProcessingBlock("ImageProcessing");
+                    break;
+                case "RGBAMergerBlock":
+                    block = new RGBAMergerBlock("RGBAMerger");
+                    break;
+                case "RGBASplitterBlock":
+                    block = new RGBASplitterBlock("RGBASplitter");
+                    break;
+                case "TextureBlock":
+                    block = new TextureBlock("Texture");
+                    break;
+                case "LightBlock":
+                    block = new LightBlock("Light");
+                    break;
+                case "FogBlock":
+                    block = new FogBlock("Fog");
+                    break;
+                case "VertexOutputBlock":
+                    block = new VertexOutputBlock("VertexOutput");
+                    break;
+                case "FragmentOutputBlock":
+                    block = new FragmentOutputBlock("FragmentOutput");
+                    break;
+                case "AddBlock":
+                    block = new AddBlock("Add");
+                    break;
+                case "ClampBlock":
+                    block = new ClampBlock("Clamp");
+                    break;
+                case "MultiplyBlock":
+                    block = new MultiplyBlock("Multiply");
+                    break;
+                case "Vector2TransformBlock":
+                    block = new Vector2TransformBlock("Vector2Transform");
+                    break;
+                case "Vector3TransformBlock":
+                    block = new Vector3TransformBlock("Vector3Transform");
+                    break;
+                case "Vector4TransformBlock":
+                    block = new Vector4TransformBlock("Vector4Transform");
+                    break;
+            }
+
+            if (block) {
+                let nodeModel = this.createNodeFromObject({ nodeMaterialBlock: block });
+                const zoomLevel = this._engine.diagramModel.getZoomLevel() / 100.0;
+
+                let x = (event.clientX - event.currentTarget.offsetLeft - this._engine.diagramModel.getOffsetX()) / zoomLevel;
+                let y = (event.clientY - event.currentTarget.offsetTop - this._engine.diagramModel.getOffsetY()) / zoomLevel;
+                nodeModel.setPosition(x, y);
+            }
+        };
+
+        this.forceUpdate();
+    }
+
     render() {
         return (
             <Portal globalState={this.props.globalState}>
@@ -375,7 +518,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                     }
                 }>
                     {/* Node creation menu */}
-                    <NodeListComponent globalState={this.props.globalState} onAddValueNode={b => this.addValueNode(b)} onAddNodeFromClass={b => this.addNodeFromClass(b)} />
+                    <NodeListComponent globalState={this.props.globalState} />
 
                     <div id="leftGrab"
                         onPointerDown={evt => this.onPointerDown(evt)}
@@ -384,7 +527,16 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
                     ></div>
 
                     {/* The node graph diagram */}
-                    <DiagramWidget deleteKeys={[46]} ref={"test"} inverseZoom={true} className="diagram-container" diagramEngine={this._engine} maxNumberPointsPerLink={0} />
+                    <div className="diagram-container"
+                        onDrop={event => {
+                            this.emitNewBlock(event);
+                        }}
+                        onDragOver={event => {
+                            event.preventDefault();
+                        }}
+                    >
+                        <DiagramWidget className="diagram" deleteKeys={[46]} ref={"test"} inverseZoom={true} diagramEngine={this._engine} maxNumberPointsPerLink={0} />
+                    </div>
 
                     <div id="rightGrab"
                         onPointerDown={evt => this.onPointerDown(evt)}

+ 7 - 0
nodeEditor/src/main.scss

@@ -28,6 +28,13 @@
     grid-row: 1;
     grid-column: 3;
     background: #222222;
+    width: 100%;
+    height: 100%;
+
+    .diagram {
+        width: 100%;
+        height: 100%;
+    }
 }
 
 #propertyTab {

+ 23 - 0
nodeEditor/src/sharedComponents/draggableLineComponent.tsx

@@ -0,0 +1,23 @@
+import * as React from "react";
+
+export interface IButtonLineComponentProps {
+    data: string;
+}
+
+export class DraggableLineComponent extends React.Component<IButtonLineComponentProps> {
+    constructor(props: IButtonLineComponentProps) {
+        super(props);
+    }
+
+    render() {
+        return (
+            <div className="draggableLine"
+                draggable={true}
+                onDragStart={event => {
+                    event.dataTransfer.setData("babylonjs-material-node", this.props.data);
+                }}>
+                {this.props.data}
+            </div>
+        );
+    }
+}

+ 4 - 3
package.json

@@ -7,7 +7,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.1.0-alpha.6",
+    "version": "4.1.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -35,7 +35,6 @@
     },
     "readme": "Babylon.js is a 3D engine based on webgl and javascript",
     "readmeFilename": "README.md",
-    "dependencies": {},
     "devDependencies": {
         "@fortawesome/fontawesome-svg-core": "~1.2.8",
         "@fortawesome/free-regular-svg-icons": "~5.4.1",
@@ -47,6 +46,7 @@
         "@types/react": "~16.7.3",
         "@types/react-dom": "~16.0.9",
         "@types/sinon": "^4.1.3",
+        "@types/dagre": "^0.7.42",
         "ajv": "^6.9.1",
         "awesome-typescript-loader": "^5.2.1",
         "base64-font-loader": "0.0.4",
@@ -87,10 +87,11 @@
         "react-contextmenu": "~2.10.0",
         "react-dom": "~16.6.3",
         "sass-loader": "^7.1.0",
-        "shelljs": "^0.7.8",
+        "shelljs": "^0.8.3",
         "sinon": "^6.1.4",
         "split.js": "^1.5.9",
         "storm-react-diagrams": "^5.2.1",
+        "dagre": "0.8.4",
         "style-loader": "^0.21.0",
         "through2": "~2.0.3",
         "ts-loader": "^5.2.1",

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

@@ -1209,6 +1209,7 @@ export class _GLTFMaterialExporter {
 
         let extension = mimeType === ImageMimeType.JPEG ? '.jpeg' : '.png';
         let textureName = baseTextureName + extension;
+        let originalTextureName = textureName;
         if (textureName in imageData) {
             textureName = `${baseTextureName}_${Tools.RandomId()}${extension}`;
         }
@@ -1221,7 +1222,7 @@ export class _GLTFMaterialExporter {
             };
             let foundIndex: Nullable<number> = null;
             for (let i = 0; i < images.length; ++i) {
-                if (images[i].uri === textureName) {
+                if (images[i].uri === originalTextureName) {
                     foundIndex = i;
                     break;
                 }

+ 4 - 0
src/Cameras/XR/index.ts

@@ -2,5 +2,9 @@ export * from "./webXRCamera";
 export * from "./webXREnterExitUI";
 export * from "./webXRExperienceHelper";
 export * from "./webXRInput";
+export * from "./webXRControllerTeleportation";
+export * from "./webXRControllerPointerSelection";
+export * from "./webXRControllerModelLoader";
+export * from "./webXRController";
 export * from "./webXRManagedOutputCanvas";
 export * from "./webXRSessionManager";

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

@@ -64,16 +64,16 @@ export class WebXRCamera extends FreeCamera {
      */
     public updateFromXRSessionManager(xrSessionManager: WebXRSessionManager) {
         // Ensure all frame data is available
-        if (!xrSessionManager._currentXRFrame || !xrSessionManager._currentXRFrame.getDevicePose) {
+        if (!xrSessionManager.currentFrame || !xrSessionManager.currentFrame.getViewerPose) {
             return false;
         }
-        var pose = xrSessionManager._currentXRFrame.getDevicePose(xrSessionManager._frameOfReference);
-        if (!pose || !pose.poseModelMatrix) {
+        var pose = xrSessionManager.currentFrame.getViewerPose(xrSessionManager.referenceSpace);
+        if (!pose || !pose.transform || !pose.transform.matrix) {
             return false;
         }
 
         // Update the parent cameras matrix
-        Matrix.FromFloat32ArrayToRefScaled(pose.poseModelMatrix, 0, 1, WebXRCamera._TmpMatrix);
+        Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, WebXRCamera._TmpMatrix);
         if (!this._scene.useRightHandedSystem) {
             WebXRCamera._TmpMatrix.toggleModelMatrixHandInPlace();
         }
@@ -83,10 +83,10 @@ export class WebXRCamera extends FreeCamera {
         this.computeWorldMatrix();
 
         // Update camera rigs
-        this._updateNumberOfRigCameras(xrSessionManager._currentXRFrame.views.length);
-        xrSessionManager._currentXRFrame.views.forEach((view, i) => {
+        this._updateNumberOfRigCameras(pose.views.length);
+        pose.views.forEach((view: any, i: number) => {
             // Update view/projection matrix
-            Matrix.FromFloat32ArrayToRefScaled(pose.getViewMatrix(view), 0, 1, this.rigCameras[i]._computedViewMatrix);
+            Matrix.FromFloat32ArrayToRefScaled(view.transform.matrix, 0, 1, this.rigCameras[i]._computedViewMatrix);
             Matrix.FromFloat32ArrayToRefScaled(view.projectionMatrix, 0, 1, this.rigCameras[i]._projectionMatrix);
             if (!this._scene.useRightHandedSystem) {
                 this.rigCameras[i]._computedViewMatrix.toggleModelMatrixHandInPlace();
@@ -94,13 +94,15 @@ export class WebXRCamera extends FreeCamera {
             }
 
             // Update viewport
-            var viewport = xrSessionManager._xrSession.baseLayer.getViewport(view);
-            var width = xrSessionManager._xrSession.baseLayer.framebufferWidth;
-            var height = xrSessionManager._xrSession.baseLayer.framebufferHeight;
-            this.rigCameras[i].viewport.width = viewport.width / width;
-            this.rigCameras[i].viewport.height = viewport.height / height;
-            this.rigCameras[i].viewport.x = viewport.x / width;
-            this.rigCameras[i].viewport.y = viewport.y / height;
+            if (xrSessionManager.session.renderState.baseLayer) {
+                var viewport = xrSessionManager.session.renderState.baseLayer.getViewport(view);
+                var width = xrSessionManager.session.renderState.baseLayer.framebufferWidth;
+                var height = xrSessionManager.session.renderState.baseLayer.framebufferHeight;
+                this.rigCameras[i].viewport.width = viewport.width / width;
+                this.rigCameras[i].viewport.height = viewport.height / height;
+                this.rigCameras[i].viewport.x = viewport.x / width;
+                this.rigCameras[i].viewport.y = viewport.y / height;
+            }
 
             // Set cameras to render to the session's render target
             this.rigCameras[i].outputRenderTarget = xrSessionManager._sessionRenderTargetTexture;

+ 116 - 0
src/Cameras/XR/webXRController.ts

@@ -0,0 +1,116 @@
+import { Nullable } from "../../types";
+import { Observable } from "../../Misc/observable";
+import { AbstractMesh } from "../../Meshes/abstractMesh";
+import { Matrix, Quaternion, Vector3 } from '../../Maths/math';
+import { Ray } from '../../Culling/ray';
+import { Scene } from '../../scene';
+/**
+ * Represents an XR input
+ */
+export class WebXRController {
+    /**
+     * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
+     */
+    public grip?: AbstractMesh;
+    /**
+     * Pointer which can be used to select objects or attach a visible laser to
+     */
+    public pointer: AbstractMesh;
+
+    /**
+     * Event that fires when the controller is removed/disposed
+     */
+    public onDisposeObservable = new Observable<{}>();
+
+    private _tmpMatrix = new Matrix();
+    private _tmpQuaternion = new Quaternion();
+    private _tmpVector = new Vector3();
+
+    /**
+     * Creates the controller
+     * @see https://doc.babylonjs.com/how_to/webxr
+     * @param scene the scene which the controller should be associated to
+     * @param inputSource the underlying input source for the controller
+     * @param parentContainer parent that the controller meshes should be children of
+     */
+    constructor(
+        private scene: Scene,
+        /** The underlying input source for the controller  */
+        public inputSource: XRInputSource,
+        private parentContainer: Nullable<AbstractMesh> = null)
+    {
+        this.pointer = new AbstractMesh("controllerPointer", scene);
+        if (parentContainer) {
+            parentContainer.addChild(this.pointer);
+        }
+
+        if (this.inputSource.gripSpace) {
+            this.grip = new AbstractMesh("controllerGrip", this.scene);
+            if (this.parentContainer) {
+                this.parentContainer.addChild(this.grip);
+            }
+        }
+    }
+
+    /**
+     * Updates the controller pose based on the given XRFrame
+     * @param xrFrame xr frame to update the pose with
+     * @param referenceSpace reference space to use
+     */
+    public updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace) {
+        let pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpace);
+
+        // Update the pointer mesh
+        if (pose) {
+            Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
+            if (!this.pointer.getScene().useRightHandedSystem) {
+                this._tmpMatrix.toggleModelMatrixHandInPlace();
+            }
+            if (!this.pointer.rotationQuaternion) {
+                this.pointer.rotationQuaternion = new Quaternion();
+            }
+            this._tmpMatrix.decompose(this.pointer.scaling, this.pointer.rotationQuaternion!, this.pointer.position);
+        }
+
+        // Update the grip mesh if it exists
+        if (this.inputSource.gripSpace && this.grip) {
+            let pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
+            if (pose) {
+                Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
+                if (!this.grip.getScene().useRightHandedSystem) {
+                    this._tmpMatrix.toggleModelMatrixHandInPlace();
+                }
+                if (!this.grip.rotationQuaternion) {
+                    this.grip.rotationQuaternion = new Quaternion();
+                }
+                this._tmpMatrix.decompose(this.grip.scaling, this.grip.rotationQuaternion!, this.grip.position);
+            }
+        }
+    }
+
+    /**
+     * Gets a world space ray coming from the controller
+     * @param result the resulting ray
+     */
+    public getWorldPointerRayToRef(result: Ray) {
+        // Force update to ensure picked point is synced with ray
+        let worldMatrix = this.pointer.computeWorldMatrix(true);
+        worldMatrix.decompose(undefined, this._tmpQuaternion, undefined);
+        this._tmpVector.set(0, 0, 1);
+        this._tmpVector.rotateByQuaternionToRef(this._tmpQuaternion, this._tmpVector);
+        result.origin = this.pointer.absolutePosition;
+        result.direction.copyFrom(this._tmpVector);
+        result.length = 1000;
+    }
+
+    /**
+     * Disposes of the object
+     */
+    dispose() {
+        if (this.grip) {
+            this.grip.dispose();
+        }
+        this.pointer.dispose();
+        this.onDisposeObservable.notifyObservers({});
+    }
+}

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

@@ -0,0 +1,44 @@
+import { Quaternion } from '../../Maths/math';
+import { WindowsMotionController } from '../../Gamepads/Controllers/windowsMotionController';
+import { OculusTouchController } from '../../Gamepads/Controllers/oculusTouchController';
+import { WebXRInput } from './webXRInput';
+
+/**
+ * Loads a controller model and adds it as a child of the WebXRControllers grip when the controller is created
+ */
+export class WebXRControllerModelLoader {
+    /**
+     * Creates the WebXRControllerModelLoader
+     * @param input xr input that creates the controllers
+     */
+    constructor(input: WebXRInput) {
+        input.onControllerAddedObservable.add((c) => {
+            if (c.inputSource.gamepad && c.inputSource.gamepad.id === "oculus-touch") {
+                let controllerModel = new OculusTouchController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness;
+                controllerModel.isXR = true;
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m) => {
+                    controllerModel.mesh!.parent = c.grip!;
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+                });
+            }else if (c.inputSource.gamepad && c.inputSource.gamepad.id === "oculus-quest") {
+                OculusTouchController._IsQuest = true;
+                let controllerModel = new OculusTouchController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness;
+                controllerModel.isXR = true;
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m) => {
+                    controllerModel.mesh!.parent = c.grip!;
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(Math.PI / -4, Math.PI, 0);
+                });
+            }else {
+                let controllerModel = new WindowsMotionController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness;
+                controllerModel.isXR = true;
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m) => {
+                    controllerModel.mesh!.parent = c.grip!;
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
+                });
+            }
+        });
+    }
+}

+ 119 - 0
src/Cameras/XR/webXRControllerPointerSelection.ts

@@ -0,0 +1,119 @@
+import { Nullable } from "../../types";
+import {  Vector3, Color3, Axis } from '../../Maths/math';
+import { Mesh } from '../../Meshes/mesh';
+import { Ray } from '../../Culling/ray';
+import { StandardMaterial } from '../../Materials/standardMaterial';
+import { WebXRInput } from './webXRInput';
+
+/**
+ * Handles pointer input automatically for the pointer of XR controllers
+ */
+export class WebXRControllerPointerSelection {
+    private static _idCounter = 0;
+    private _tmpRay = new Ray(new Vector3(), new Vector3());
+
+    /**
+     * Creates a WebXRControllerPointerSelection
+     * @param input input manager to setup pointer selection
+     */
+    constructor(input: WebXRInput) {
+        input.onControllerAddedObservable.add((controller) => {
+            let scene = controller.pointer.getScene();
+
+            let laserPointer: Mesh;
+            let cursorMesh: Mesh;
+            let triggerDown = false;
+            let id: number;
+            id = WebXRControllerPointerSelection._idCounter++;
+
+            // Create a laser pointer for the XR controller
+            laserPointer = Mesh.CreateCylinder("laserPointer", 1, 0.0002, 0.004, 20, 1, scene, false);
+            laserPointer.parent = controller.pointer;
+            let laserPointerMaterial = new StandardMaterial("laserPointerMat", scene);
+            laserPointerMaterial.emissiveColor = new Color3(0.7, 0.7, 0.7);
+            laserPointerMaterial.alpha = 0.6;
+            laserPointer.material = laserPointerMaterial;
+            laserPointer.rotation.x = Math.PI / 2;
+            this._updatePointerDistance(laserPointer, 1);
+            laserPointer.isPickable = false;
+
+            // Create a gaze tracker for the  XR controlelr
+            cursorMesh = Mesh.CreateTorus("gazeTracker", 0.0035 * 3, 0.0025 * 3, 20, scene, false);
+            cursorMesh.bakeCurrentTransformIntoVertices();
+            cursorMesh.isPickable = false;
+            cursorMesh.isVisible = false;
+            let targetMat = new StandardMaterial("targetMat", scene);
+            targetMat.specularColor = Color3.Black();
+            targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
+            targetMat.backFaceCulling = false;
+            cursorMesh.material = targetMat;
+
+            let renderObserver = scene.onBeforeRenderObservable.add(() => {
+                // Every frame check collisions/input
+                controller.getWorldPointerRayToRef(this._tmpRay);
+                let pick = scene.pickWithRay(this._tmpRay);
+                if (pick) {
+                    if (controller.inputSource.gamepad && controller.inputSource.gamepad.buttons[0] && controller.inputSource.gamepad.buttons[0].value > 0.7) {
+                        if (!triggerDown) {
+                            scene.simulatePointerDown(pick, { pointerId: id });
+                        }
+                        triggerDown = true;
+                    }else {
+                        if (triggerDown) {
+                            scene.simulatePointerUp(pick, { pointerId: id });
+                        }
+                        triggerDown = false;
+                    }
+                    scene.simulatePointerMove(pick, { pointerId: id });
+                }
+
+                if (pick && pick.pickedPoint && pick.hit) {
+                    // Update laser state
+                    this._updatePointerDistance(laserPointer, pick.distance);
+
+                    // Update cursor state
+                    cursorMesh.position.copyFrom(pick.pickedPoint);
+                    cursorMesh.scaling.x = Math.sqrt(pick.distance);
+                    cursorMesh.scaling.y = Math.sqrt(pick.distance);
+                    cursorMesh.scaling.z = Math.sqrt(pick.distance);
+
+                    // To avoid z-fighting
+                    let pickNormal = this._convertNormalToDirectionOfRay(pick.getNormal(), this._tmpRay);
+                    let deltaFighting = 0.002;
+                    cursorMesh.position.copyFrom(pick.pickedPoint);
+                    if (pickNormal) {
+                        let axis1 = Vector3.Cross(Axis.Y, pickNormal);
+                        let axis2 = Vector3.Cross(pickNormal, axis1);
+                        Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, cursorMesh.rotation);
+                        cursorMesh.position.addInPlace(pickNormal.scale(deltaFighting));
+                    }
+                    cursorMesh.isVisible = true;
+                }else {
+                    cursorMesh.isVisible = false;
+                }
+            });
+
+            controller.onDisposeObservable.addOnce(() => {
+                laserPointer.dispose();
+                cursorMesh.dispose();
+
+                scene.onBeforeRenderObservable.remove(renderObserver);
+            });
+        });
+    }
+
+    private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
+        if (normal) {
+            let angle = Math.acos(Vector3.Dot(normal, ray.direction));
+            if (angle < Math.PI / 2) {
+                normal.scaleInPlace(-1);
+            }
+        }
+        return normal;
+    }
+
+    private _updatePointerDistance(_laserPointer: Mesh, distance: number = 100) {
+        _laserPointer.scaling.y = distance;
+        _laserPointer.position.z = distance / 2;
+    }
+}

+ 181 - 0
src/Cameras/XR/webXRControllerTeleportation.ts

@@ -0,0 +1,181 @@
+import { AbstractMesh } from "../../Meshes/abstractMesh";
+import { Quaternion, Vector3 } from '../../Maths/math';
+import { Mesh } from '../../Meshes/mesh';
+import { Ray } from '../../Culling/ray';
+import { StandardMaterial } from '../../Materials/standardMaterial';
+import { DynamicTexture } from '../../Materials/Textures/dynamicTexture';
+import { EasingFunction, SineEase } from '../../Animations/easing';
+import { Animation } from '../../Animations/animation';
+import { WebXRInput } from './webXRInput';
+
+/**
+ * Enables teleportation
+ */
+export class WebXRControllerTeleportation {
+    private _teleportationFillColor: string = "#444444";
+    private _teleportationBorderColor: string = "#FFFFFF";
+
+    private _tmpRay = new Ray(new Vector3(), new Vector3());
+    private _tmpVector = new Vector3();
+
+    /**
+     * Creates a WebXRControllerTeleportation
+     * @param input input manager to add teleportation to
+     * @param floorMeshes floormeshes which can be teleported to
+     */
+    constructor(input: WebXRInput, floorMeshes: Array<AbstractMesh> = []) {
+        input.onControllerAddedObservable.add((c) => {
+            let scene = c.pointer.getScene();
+
+            let forwardReadyToTeleport = false;
+            let backwardReadyToTeleport = false;
+            let leftReadyToTeleport = false;
+            let rightReadyToTeleport = false;
+
+            // Teleport target abd it's animation
+            let teleportationTarget = Mesh.CreateGround("teleportationTarget", 2, 2, 2, scene);
+            teleportationTarget.isPickable = false;
+            let length = 512;
+            let dynamicTexture = new DynamicTexture("DynamicTexture", length, scene, true);
+            dynamicTexture.hasAlpha = true;
+            let context = dynamicTexture.getContext();
+            let centerX = length / 2;
+            let centerY = length / 2;
+            let radius = 200;
+            context.beginPath();
+            context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
+            context.fillStyle = this._teleportationFillColor;
+            context.fill();
+            context.lineWidth = 10;
+            context.strokeStyle = this._teleportationBorderColor;
+            context.stroke();
+            context.closePath();
+            dynamicTexture.update();
+            let teleportationCircleMaterial = new StandardMaterial("TextPlaneMaterial", scene);
+            teleportationCircleMaterial.diffuseTexture = dynamicTexture;
+            teleportationTarget.material = teleportationCircleMaterial;
+            let torus = Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, scene, false);
+            torus.isPickable = false;
+            torus.parent = teleportationTarget;
+            let animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
+            let keys = [];
+            keys.push({
+                frame: 0,
+                value: 0
+            });
+            keys.push({
+                frame: 30,
+                value: 0.4
+            });
+            keys.push({
+                frame: 60,
+                value: 0
+            });
+            animationInnerCircle.setKeys(keys);
+            let easingFunction = new SineEase();
+            easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
+            animationInnerCircle.setEasingFunction(easingFunction);
+            torus.animations = [];
+            torus.animations.push(animationInnerCircle);
+            scene.beginAnimation(torus, 0, 60, true);
+
+            // Handle user input on every frame
+            let renderObserver = scene.onBeforeRenderObservable.add(() => {
+                // Move the teleportationTarget to where the user is targetting to teleport to
+                if (forwardReadyToTeleport) {
+                    c.getWorldPointerRayToRef(this._tmpRay);
+                    let pick = scene.pickWithRay(this._tmpRay, (o) => {
+                        return floorMeshes.indexOf(o) !== -1;
+                    });
+                    if (pick && pick.pickedPoint) {
+                        // To avoid z-fighting
+                        teleportationTarget.position.copyFrom(pick.pickedPoint);
+                        teleportationTarget.position.y += 0.002;
+                    }
+                    teleportationTarget.isVisible = true;
+                    (<Mesh>teleportationTarget.getChildren()[0]).isVisible = true;
+                }else {
+                    teleportationTarget.isVisible = false;
+                    (<Mesh>teleportationTarget.getChildren()[0]).isVisible = false;
+                }
+
+                if (c.inputSource.gamepad) {
+                    if (c.inputSource.gamepad.axes[1]) {
+                        // Forward teleportation
+                        if (c.inputSource.gamepad.axes[1] < -0.7) {
+                            forwardReadyToTeleport = true;
+                        }else {
+                            if (forwardReadyToTeleport) {
+                                // Teleport the users feet to where they targetted
+                                this._tmpVector.copyFrom(teleportationTarget.position);
+                                this._tmpVector.y += input.baseExperience.camera.position.y;
+                                input.baseExperience.setPositionOfCameraUsingContainer(this._tmpVector);
+                            }
+                            forwardReadyToTeleport = false;
+                        }
+
+                        // Backward teleportation
+                        if (c.inputSource.gamepad.axes[1] > 0.7) {
+                            backwardReadyToTeleport = true;
+                        }else {
+                            if (backwardReadyToTeleport) {
+                                // Cast a ray down from behind the user
+                                let camMat = input.baseExperience.camera.computeWorldMatrix();
+                                let q = new Quaternion();
+                                camMat.decompose(undefined, q, this._tmpRay.origin);
+                                this._tmpVector.set(0, 0, -1);
+                                this._tmpVector.rotateByQuaternionToRef(q, this._tmpVector);
+                                this._tmpVector.y = 0;
+                                this._tmpVector.normalize();
+                                this._tmpVector.y = -1.5;
+                                this._tmpVector.normalize();
+                                this._tmpRay.direction.copyFrom(this._tmpVector);
+                                let pick = scene.pickWithRay(this._tmpRay, (o) => {
+                                    return floorMeshes.indexOf(o) !== -1;
+                                });
+
+                                if (pick && pick.pickedPoint) {
+                                    // Teleport the users feet to where they targetted
+                                    this._tmpVector.copyFrom(pick.pickedPoint);
+                                    this._tmpVector.y += input.baseExperience.camera.position.y;
+                                    input.baseExperience.setPositionOfCameraUsingContainer(this._tmpVector);
+                                }
+                            }
+                            backwardReadyToTeleport = false;
+                        }
+                    }
+
+                    if (c.inputSource.gamepad.axes[0]) {
+                        if (c.inputSource.gamepad.axes[0] < -0.7) {
+                            leftReadyToTeleport = true;
+                        }else {
+                            if (leftReadyToTeleport) {
+                                input.baseExperience.rotateCameraByQuaternionUsingContainer(Quaternion.FromEulerAngles(0, -Math.PI / 4, 0));
+                            }
+                            leftReadyToTeleport = false;
+                        }
+                        if (c.inputSource.gamepad.axes[0] > 0.7) {
+                            rightReadyToTeleport = true;
+                        }else {
+                            if (rightReadyToTeleport) {
+                                input.baseExperience.rotateCameraByQuaternionUsingContainer(Quaternion.FromEulerAngles(0, Math.PI / 4, 0));
+                            }
+                            rightReadyToTeleport = false;
+                        }
+                    }
+
+                }
+            });
+
+            c.onDisposeObservable.addOnce(() => {
+                teleportationTarget.dispose();
+                dynamicTexture.dispose();
+                teleportationCircleMaterial.dispose();
+                torus.dispose();
+
+                scene.onBeforeRenderObservable.remove(renderObserver);
+            });
+        });
+    }
+
+}

+ 104 - 0
src/Cameras/XR/webXRDefaultExperience.ts

@@ -0,0 +1,104 @@
+import { WebXRExperienceHelper } from "./webXRExperienceHelper";
+import { Scene } from '../../scene';
+import { WebXRInput } from './webXRInput';
+import { WebXRControllerModelLoader } from './webXRControllerModelLoader';
+import { WebXRControllerPointerSelection } from './webXRControllerPointerSelection';
+import { WebXRControllerTeleportation } from './webXRControllerTeleportation';
+import { WebXRManagedOutputCanvas } from './webXRManagedOutputCanvas';
+import { WebXREnterExitUI } from './webXREnterExitUI';
+import { AbstractMesh } from '../../Meshes/abstractMesh';
+/**
+ * Options for the default xr helper
+ */
+export class WebXRDefaultExperienceOptions {
+    /**
+     * Floor meshes that should be used for teleporting
+     */
+    public floorMeshes: Array<AbstractMesh>;
+}
+
+/**
+ * Default experience which provides a similar setup to the previous webVRExperience
+ */
+export class WebXRDefaultExperience {
+    /**
+     * Base experience
+     */
+    public baseExperience: WebXRExperienceHelper;
+    /**
+     * Input experience extension
+     */
+    public input: WebXRInput;
+    /**
+     * Loads the controller models
+     */
+    public controllerModelLoader: WebXRControllerModelLoader;
+    /**
+     * Enables laser pointer and selection
+     */
+    public pointerSelection: WebXRControllerPointerSelection;
+    /**
+     * Enables teleportation
+     */
+    public teleportation: WebXRControllerTeleportation;
+    /**
+     * Enables ui for enetering/exiting xr
+     */
+    public enterExitUI: WebXREnterExitUI;
+    /**
+     * Default output canvas xr should render to
+     */
+    public outputCanvas: WebXRManagedOutputCanvas;
+
+    /**
+     * Creates the default xr experience
+     * @param scene scene
+     * @param options options for basic configuration
+     * @returns resulting WebXRDefaultExperience
+     */
+    public static CreateAsync(scene: Scene, options: WebXRDefaultExperienceOptions) {
+        var result = new WebXRDefaultExperience();
+
+        // Create base experience
+        return WebXRExperienceHelper.CreateAsync(scene).then((xrHelper) => {
+            result.baseExperience = xrHelper;
+
+            // Add controller support
+            result.input = new WebXRInput(xrHelper);
+            result.controllerModelLoader = new WebXRControllerModelLoader(result.input);
+            result.pointerSelection = new WebXRControllerPointerSelection(result.input);
+            result.teleportation = new WebXRControllerTeleportation(result.input, options.floorMeshes);
+
+            // Create output canvas manager (this controls where the xr frames will be rendered)
+            result.outputCanvas = new WebXRManagedOutputCanvas(xrHelper, scene.getEngine().getRenderingCanvas() as HTMLCanvasElement);
+
+            // Create ui for entering/exiting xr
+            return WebXREnterExitUI.CreateAsync(scene, result.baseExperience, {webXRManagedOutputCanvas: result.outputCanvas});
+        }).then((ui) => {
+            result.enterExitUI = ui;
+            return result;
+        });
+    }
+
+    private constructor() {
+
+    }
+
+    /**
+     * DIsposes of the experience helper
+     */
+    public dispose() {
+        if (this.baseExperience) {
+            this.baseExperience.dispose();
+        }
+        if (this.input) {
+            this.input.dispose();
+        }
+        if (this.enterExitUI) {
+            this.enterExitUI.dispose();
+        }
+        if (this.outputCanvas) {
+            this.outputCanvas.dispose();
+        }
+    }
+}

+ 13 - 16
src/Cameras/XR/webXREnterExitUI.ts

@@ -2,6 +2,7 @@ import { Nullable } from "../../types";
 import { Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
 import { WebXRExperienceHelper, WebXRState } from "./webXRExperienceHelper";
+import { WebXRManagedOutputCanvas } from '../XR/webXRManagedOutputCanvas';
 /**
  * Button which can be used to enter a different mode of XR
  */
@@ -9,13 +10,16 @@ export class WebXREnterExitUIButton {
     /**
      * Creates a WebXREnterExitUIButton
      * @param element button element
-     * @param initializationOptions XR initialization options for the button
+     * @param sessionMode XR initialization session mode
+     * @param referenceSpaceType the type of reference space to be used
      */
     constructor(
         /** button element */
         public element: HTMLElement,
         /** XR initialization options for the button */
-        public initializationOptions: XRSessionCreationOptions
+        public sessionMode: XRSessionMode,
+        /** Reference space type */
+        public referenceSpaceType: XRReferenceSpaceType
     ) { }
     /**
      * Overwritable function which can be used to update the button's visuals when the state changes
@@ -32,7 +36,7 @@ export class WebXREnterExitUIOptions {
     /**
      * Context to enter xr with
      */
-    outputCanvasContext?: Nullable<WebGLRenderingContext>;
+    webXRManagedOutputCanvas?: Nullable<WebXRManagedOutputCanvas>;
 
     /**
      * User provided buttons to enable/disable WebXR. The system will provide default if not set
@@ -64,7 +68,7 @@ export class WebXREnterExitUI implements IDisposable {
     public static CreateAsync(scene: Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions): Promise<WebXREnterExitUI> {
         var ui = new WebXREnterExitUI(scene, options);
         var supportedPromises = ui._buttons.map((btn) => {
-            return helper.supportsSessionAsync(btn.initializationOptions);
+            return helper.sessionManager.supportsSessionAsync(btn.sessionMode);
         });
         helper.onStateChangedObservable.add((state) => {
             if (state == WebXRState.NOT_IN_XR) {
@@ -82,7 +86,9 @@ export class WebXREnterExitUI implements IDisposable {
                             return;
                         } else if (helper.state == WebXRState.NOT_IN_XR) {
                             ui._updateButtons(ui._buttons[i]);
-                            await helper.enterXRAsync(ui._buttons[i].initializationOptions, "eye-level");
+                            if (options.webXRManagedOutputCanvas) {
+                                await helper.enterXRAsync(ui._buttons[i].sessionMode, ui._buttons[i].referenceSpaceType, options.webXRManagedOutputCanvas);
+                            }
                         }
                     };
                 }
@@ -101,20 +107,11 @@ export class WebXREnterExitUI implements IDisposable {
             var hmdBtn = document.createElement("button");
             hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
             hmdBtn.innerText = "HMD";
-            this._buttons.push(new WebXREnterExitUIButton(hmdBtn, { immersive: true, outputContext: options.outputCanvasContext }));
+            this._buttons.push(new WebXREnterExitUIButton(hmdBtn, "immersive-vr", "local-floor"));
             this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
                 this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
                 this.element.innerText = activeButton === this ? "EXIT" : "HMD";
             };
-
-            var windowBtn = document.createElement("button");
-            windowBtn.style.cssText = hmdBtn.style.cssText;
-            windowBtn.innerText = "Window";
-            this._buttons.push(new WebXREnterExitUIButton(windowBtn, { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext }));
-            this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
-                this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
-                this.element.innerText = activeButton === this ? "EXIT" : "Window";
-            };
             this._updateButtons(null);
         }
 
@@ -145,4 +142,4 @@ export class WebXREnterExitUI implements IDisposable {
         }
         this.activeButtonChangedObservable.clear();
     }
-}
+}

+ 37 - 40
src/Cameras/XR/webXRExperienceHelper.ts

@@ -3,10 +3,10 @@ import { Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
 import { Quaternion, Vector3 } from "../../Maths/math";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
-import { Ray } from "../../Culling/ray";
 import { Camera } from "../../Cameras/camera";
 import { WebXRSessionManager } from "./webXRSessionManager";
 import { WebXRCamera } from "./webXRCamera";
+import { WebXRManagedOutputCanvas } from './webXRManagedOutputCanvas';
 /**
  * States of the webXR experience
  */
@@ -29,7 +29,7 @@ export enum WebXRState {
     NOT_IN_XR
 }
 /**
- * Helper class used to enable XR
+ * Base set of functionality needed to create an XR experince (WebXRSessionManager, Camera, StateManagement, etc.)
  * @see https://doc.babylonjs.com/how_to/webxr
  */
 export class WebXRExperienceHelper implements IDisposable {
@@ -59,8 +59,8 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public onStateChangedObservable = new Observable<WebXRState>();
 
-    /** @hidden */
-    public _sessionManager: WebXRSessionManager;
+    /** Session manager used to keep track of xr session */
+    public sessionManager: WebXRSessionManager;
 
     private _nonVRCamera: Nullable<Camera> = null;
     private _originalSceneAutoClear = true;
@@ -74,7 +74,7 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public static CreateAsync(scene: Scene): Promise<WebXRExperienceHelper> {
         var helper = new WebXRExperienceHelper(scene);
-        return helper._sessionManager.initializeAsync().then(() => {
+        return helper.sessionManager.initializeAsync().then(() => {
             helper._supported = true;
             return helper;
         }).catch(() => {
@@ -88,9 +88,13 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     private constructor(private scene: Scene) {
         this.camera = new WebXRCamera("", scene);
-        this._sessionManager = new WebXRSessionManager(scene);
-        this.container = new AbstractMesh("", scene);
+        this.sessionManager = new WebXRSessionManager(scene);
+        this.container = new AbstractMesh("WebXR Container", scene);
         this.camera.parent = this.container;
+
+        scene.onDisposeObservable.add(() => {
+            this.exitXRAsync();
+        });
     }
 
     /**
@@ -99,22 +103,30 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public exitXRAsync() {
         this._setState(WebXRState.EXITING_XR);
-        return this._sessionManager.exitXRAsync();
+        return this.sessionManager.exitXRAsync();
     }
 
     /**
      * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
      * @param sessionCreationOptions options for the XR session
-     * @param frameOfReference frame of reference of the XR session
+     * @param referenceSpaceType frame of reference of the XR session
+     * @param outputCanvas the output canvas that will be used to enter XR mode
      * @returns promise that resolves after xr mode has entered
      */
-    public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
+    public enterXRAsync(sessionCreationOptions: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, outputCanvas: WebXRManagedOutputCanvas) {
         if (!this._supported) {
             throw "XR session not supported by this browser";
         }
         this._setState(WebXRState.ENTERING_XR);
-
-        return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(() => {
+        return this.sessionManager.initializeSessionAsync(sessionCreationOptions).then(() => {
+            return this.sessionManager.setReferenceSpaceAsync(referenceSpaceType);
+        }).then(() => {
+            return outputCanvas.initializeXRLayerAsync(this.sessionManager.session);
+        }).then(() => {
+            return this.sessionManager.updateRenderStateAsync({baseLayer: outputCanvas.xrLayer});
+        }).then(() => {
+            return this.sessionManager.startRenderingToXRAsync();
+        }).then(() => {
             // Cache pre xr scene settings
             this._originalSceneAutoClear = this.scene.autoClear;
             this._nonVRCamera = this.scene.activeCamera;
@@ -123,11 +135,11 @@ export class WebXRExperienceHelper implements IDisposable {
             this.scene.autoClear = false;
             this.scene.activeCamera = this.camera;
 
-            this._sessionManager.onXRFrameObservable.add(() => {
-                this.camera.updateFromXRSessionManager(this._sessionManager);
+            this.sessionManager.onXRFrameObservable.add(() => {
+                this.camera.updateFromXRSessionManager(this.sessionManager);
             });
 
-            this._sessionManager.onXRSessionEnded.addOnce(() => {
+            this.sessionManager.onXRSessionEnded.addOnce(() => {
                 // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
                 this.camera.rigCameras.forEach((c) => {
                     c.outputRenderTarget = null;
@@ -136,21 +148,18 @@ export class WebXRExperienceHelper implements IDisposable {
                 // Restore scene settings
                 this.scene.autoClear = this._originalSceneAutoClear;
                 this.scene.activeCamera = this._nonVRCamera;
-                this._sessionManager.onXRFrameObservable.clear();
 
                 this._setState(WebXRState.NOT_IN_XR);
             });
-            this._setState(WebXRState.IN_XR);
-        });
-    }
 
-    /**
-     * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
-     * @param ray ray to cast into the environment
-     * @returns Promise which resolves with a collision point in the environment if it exists
-     */
-    public environmentPointHitTestAsync(ray: Ray): Promise<Nullable<Vector3>> {
-        return this._sessionManager.environmentPointHitTestAsync(ray);
+            // Wait until the first frame arrives before setting state to in xr
+            this.sessionManager.onXRFrameObservable.addOnce(() => {
+                this._setState(WebXRState.IN_XR);
+            });
+        }).catch((e: any) => {
+            console.log(e);
+            console.log(e.message);
+        });
     }
 
     /**
@@ -177,24 +186,12 @@ export class WebXRExperienceHelper implements IDisposable {
     }
 
     /**
-     * Checks if the creation options are supported by the xr session
-     * @param options creation options
-     * @returns true if supported
-     */
-    public supportsSessionAsync(options: XRSessionCreationOptions) {
-        if (!this._supported) {
-            return Promise.resolve(false);
-        }
-        return this._sessionManager.supportsSessionAsync(options);
-    }
-
-    /**
      * Disposes of the experience helper
      */
     public dispose() {
         this.camera.dispose();
         this.container.dispose();
         this.onStateChangedObservable.clear();
-        this._sessionManager.dispose();
+        this.sessionManager.dispose();
     }
-}
+}

+ 74 - 77
src/Cameras/XR/webXRInput.ts

@@ -1,40 +1,8 @@
 import { Nullable } from "../../types";
-import { Observer } from "../../Misc/observable";
-import { Matrix, Quaternion } from "../../Maths/math";
-import { IDisposable, Scene } from "../../scene";
-import { AbstractMesh } from "../../Meshes/abstractMesh";
-import { WebXRExperienceHelper } from "./webXRExperienceHelper";
-/**
- * Represents an XR input
- */
-export class WebXRController {
-    /**
-     * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
-     */
-    public grip?: AbstractMesh;
-    /**
-     * Pointer which can be used to select objects or attach a visible laser to
-     */
-    public pointer: AbstractMesh;
-
-    /**
-     * Creates the controller
-     * @see https://doc.babylonjs.com/how_to/webxr
-     * @param scene the scene which the controller should be associated to
-     */
-    constructor(scene: Scene) {
-        this.pointer = new AbstractMesh("controllerPointer", scene);
-    }
-    /**
-     * Disposes of the object
-     */
-    dispose() {
-        if (this.grip) {
-            this.grip.dispose();
-        }
-        this.pointer.dispose();
-    }
-}
+import { Observer, Observable } from "../../Misc/observable";
+import { IDisposable } from "../../scene";
+import { WebXRExperienceHelper, WebXRState } from "./webXRExperienceHelper";
+import { WebXRController } from './webXRController';
 
 /**
  * XR input used to track XR inputs such as controllers/rays
@@ -44,58 +12,86 @@ export class WebXRInput implements IDisposable {
      * XR controllers being tracked
      */
     public controllers: Array<WebXRController> = [];
-    private _tmpMatrix = new Matrix();
     private _frameObserver: Nullable<Observer<any>>;
+    private _stateObserver: Nullable<Observer<any>>;
+    /**
+     * Event when a controller has been connected/added
+     */
+    public onControllerAddedObservable = new Observable<WebXRController>();
+    /**
+     * Event when a controller has been removed/disconnected
+     */
+    public onControllerRemovedObservable = new Observable<WebXRController>();
 
     /**
      * Initializes the WebXRInput
-     * @param helper experience helper which the input should be created for
+     * @param baseExperience experience helper which the input should be created for
      */
-    public constructor(private helper: WebXRExperienceHelper) {
-        this._frameObserver = helper._sessionManager.onXRFrameObservable.add(() => {
-            if (!helper._sessionManager._currentXRFrame || !helper._sessionManager._currentXRFrame.getDevicePose) {
+    public constructor(
+        /**
+         * Base experience the input listens to
+         */
+        public baseExperience: WebXRExperienceHelper
+    ) {
+        // 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._frameObserver = baseExperience.sessionManager.onXRFrameObservable.add(() => {
+            if (!baseExperience.sessionManager.currentFrame) {
                 return;
             }
 
-            var xrFrame = helper._sessionManager._currentXRFrame;
-            var inputSources = helper._sessionManager._xrSession.getInputSources();
+            // Start listing to input add/remove event
+            if (this.controllers.length == 0 && baseExperience.sessionManager.session.inputSources) {
+                this._addAndRemoveControllers(baseExperience.sessionManager.session.inputSources, []);
+                baseExperience.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
+            }
+
+            // Update controller pose info
+            this.controllers.forEach((controller) => {
+                controller.updateFromXRFrame(baseExperience.sessionManager.currentFrame!, baseExperience.sessionManager.referenceSpace);
+            });
 
-            inputSources.forEach((input, i) => {
-                let inputPose = xrFrame.getInputPose(input, helper._sessionManager._frameOfReference);
-                if (inputPose) {
-                    if (this.controllers.length <= i) {
-                        this.controllers.push(new WebXRController(helper.container.getScene()));
-                    }
-                    var controller = this.controllers[i];
+        });
+    }
 
-                    // Manage the grip if it exists
-                    if (inputPose.gripMatrix) {
-                        if (!controller.grip) {
-                            controller.grip = new AbstractMesh("controllerGrip", helper.container.getScene());
-                        }
-                        Matrix.FromFloat32ArrayToRefScaled(inputPose.gripMatrix, 0, 1, this._tmpMatrix);
-                        if (!controller.grip.getScene().useRightHandedSystem) {
-                            this._tmpMatrix.toggleModelMatrixHandInPlace();
-                        }
-                        if (!controller.grip.rotationQuaternion) {
-                            controller.grip.rotationQuaternion = new Quaternion();
-                        }
-                        this._tmpMatrix.decompose(controller.grip.scaling, controller.grip.rotationQuaternion, controller.grip.position);
-                    }
+    private _onInputSourcesChange = (event: XRInputSourceChangeEvent) => {
+        this._addAndRemoveControllers(event.added, event.removed);
+    }
 
-                    // Manager pointer of controller
-                    Matrix.FromFloat32ArrayToRefScaled(inputPose.targetRay.transformMatrix, 0, 1, this._tmpMatrix);
-                    if (!controller.pointer.getScene().useRightHandedSystem) {
-                        this._tmpMatrix.toggleModelMatrixHandInPlace();
-                    }
-                    if (!controller.pointer.rotationQuaternion) {
-                        controller.pointer.rotationQuaternion = new Quaternion();
-                    }
-                    this._tmpMatrix.decompose(controller.pointer.scaling, controller.pointer.rotationQuaternion, controller.pointer.position);
-                }
-            });
+    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; });
+        for (let input of addInputs) {
+            if (sources.indexOf(input) === -1) {
+                let controller = new WebXRController(this.baseExperience.camera._scene, input, this.baseExperience.container);
+                this.controllers.push(controller);
+                this.onControllerAddedObservable.notifyObservers(controller);
+            }
+        }
+
+        // Remove and dispose of controllers to be disposed
+        let keepControllers: Array<WebXRController> = [];
+        let removedControllers: Array<WebXRController> = [];
+        this.controllers.forEach((c) => {
+            if (removeInputs.indexOf(c.inputSource) === -1) {
+                keepControllers.push(c);
+            }else {
+                removedControllers.push(c);
+            }
         });
+        this.controllers = keepControllers;
+        removedControllers.forEach((c) => {
+            this.onControllerRemovedObservable.notifyObservers(c);
+            c.dispose();
+        });
+
     }
+
     /**
      * Disposes of the object
      */
@@ -103,6 +99,7 @@ export class WebXRInput implements IDisposable {
         this.controllers.forEach((c) => {
             c.dispose();
         });
-        this.helper._sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        this.baseExperience.sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        this.baseExperience.onStateChangedObservable.remove(this._stateObserver);
     }
-}
+}

+ 27 - 8
src/Cameras/XR/webXRManagedOutputCanvas.ts

@@ -9,16 +9,32 @@ export class WebXRManagedOutputCanvas implements IDisposable {
     /**
      * xrpresent context of the canvas which can be used to display/mirror xr content
      */
-    public canvasContext: Nullable<WebGLRenderingContext> = null;
+    public canvasContext: WebGLRenderingContext;
+    /**
+     * xr layer for the canvas
+     */
+    public xrLayer: Nullable<XRWebGLLayer> = null;
+
+    /**
+     * Initializes the xr layer for the session
+     * @param xrSession xr session
+     * @returns a promise that will resolve once the XR Layer has been created
+     */
+    public initializeXRLayerAsync(xrSession: any) {
+        return (this.canvasContext as any).makeXRCompatible().then(() => {
+            this.xrLayer = new XRWebGLLayer(xrSession, this.canvasContext);
+        });
+    }
+
     /**
      * Initializes the canvas to be added/removed upon entering/exiting xr
      * @param helper the xr experience helper used to trigger adding/removing of the canvas
      * @param canvas The canvas to be added/removed (If not specified a full screen canvas will be created)
      */
-    public constructor(helper: WebXRExperienceHelper, canvas?: HTMLCanvasElement) {
+    constructor(private helper: WebXRExperienceHelper, canvas?: HTMLCanvasElement) {
         if (!canvas) {
             canvas = document.createElement('canvas');
-            canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #000000;";
+            canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:90%;height:100%;background-color: #000000;";
         }
         this._setManagedOutputCanvas(canvas);
         helper.onStateChangedObservable.add((stateInfo) => {
@@ -42,22 +58,25 @@ export class WebXRManagedOutputCanvas implements IDisposable {
         this._removeCanvas();
         if (!canvas) {
             this._canvas = null;
-            this.canvasContext = null;
+            (this.canvasContext as any) = null;
         } else {
             this._canvas = canvas;
-            this.canvasContext = <any>this._canvas.getContext('xrpresent');
+            this.canvasContext = <any>this._canvas.getContext('webgl');
+            if (!this.canvasContext) {
+                this.canvasContext = <any>this._canvas.getContext('webgl2');
+            }
         }
     }
 
     private _addCanvas() {
-        if (this._canvas) {
+        if (this._canvas && this._canvas !== this.helper.container.getScene().getEngine().getRenderingCanvas()) {
             document.body.appendChild(this._canvas);
         }
     }
 
     private _removeCanvas() {
-        if (this._canvas && document.body.contains(this._canvas)) {
+        if (this._canvas && document.body.contains(this._canvas) && this._canvas !== this.helper.container.getScene().getEngine().getRenderingCanvas()) {
             document.body.removeChild(this._canvas);
         }
     }
-}
+}

+ 92 - 89
src/Cameras/XR/webXRSessionManager.ts

@@ -2,12 +2,10 @@ import { Logger } from "../../Misc/logger";
 import { Observable } from "../../Misc/observable";
 import { Nullable } from "../../types";
 import { IDisposable, Scene } from "../../scene";
-import { Vector3, Matrix } from "../../Maths/math";
 import { InternalTexture } from "../../Materials/Textures/internalTexture";
 import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
-import { Ray } from "../../Culling/ray";
 /**
- * Manages an XRSession
+ * Manages an XRSession to work with Babylon's engine
  * @see https://doc.babylonjs.com/how_to/webxr
  */
 export class WebXRSessionManager implements IDisposable {
@@ -20,17 +18,25 @@ export class WebXRSessionManager implements IDisposable {
      */
     public onXRSessionEnded: Observable<any> = new Observable<any>();
 
-    /** @hidden */
-    public _xrSession: XRSession;
-    /** @hidden */
-    public _frameOfReference: XRFrameOfReference;
+    /**
+     * Underlying xr session
+     */
+    public session: XRSession;
+
+    /**
+     * Type of reference space used when creating the session
+     */
+    public referenceSpace: XRReferenceSpace;
+
     /** @hidden */
     public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
-    /** @hidden */
-    public _currentXRFrame: Nullable<XRFrame>;
+
+    /**
+     * Current XR frame
+     */
+    public currentFrame: Nullable<XRFrame>;
     private _xrNavigator: any;
-    private _xrDevice: XRDevice;
-    private _tmpMatrix = new Matrix();
+    private baseLayer: Nullable<XRWebGLLayer> = null;
 
     /**
      * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
@@ -52,26 +58,20 @@ export class WebXRSessionManager implements IDisposable {
         if (!this._xrNavigator.xr) {
             return Promise.reject("webXR not supported by this browser");
         }
-        // Request the webXR device
-        return this._xrNavigator.xr.requestDevice().then((device: XRDevice) => {
-            this._xrDevice = device;
-            return (<any>this.scene.getEngine()._gl).setCompatibleXRDevice(this._xrDevice);
-        });
+        return Promise.resolve();
     }
 
     /**
-     * Enters XR with the desired XR session options, this must be done with a user action (eg. button click event)
-     * @param sessionCreationOptions xr options to create the session with
-     * @param frameOfReferenceType option to configure how the xr pose is expressed
-     * @returns Promise which resolves after it enters XR
+     * Initializes an xr session
+     * @param xrSessionMode mode to initialize
+     * @returns a promise which will resolve once the session has been initialized
      */
-    public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
-        // initialize session
-        return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
-            this._xrSession = session;
+    public initializeSessionAsync(xrSessionMode: XRSessionMode) {
+        return this._xrNavigator.xr.requestSession(xrSessionMode).then((session: XRSession) => {
+            this.session = session;
 
             // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
-            this._xrSession.addEventListener("end", () => {
+            this.session.addEventListener("end", () => {
                 // Remove render target texture and notify frame obervers
                 this._sessionRenderTargetTexture = null;
 
@@ -83,83 +83,83 @@ export class WebXRSessionManager implements IDisposable {
                 this.onXRSessionEnded.notifyObservers(null);
                 this.scene.getEngine()._renderLoop();
             }, { once: true });
+        });
+    }
 
-            this._xrSession.baseLayer = new XRWebGLLayer(this._xrSession, this.scene.getEngine()._gl);
-            return this._xrSession.requestFrameOfReference(frameOfReferenceType);
-        }).then((frameOfRef: any) => {
-            this._frameOfReference = frameOfRef;
-            // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
-            this.scene.getEngine().customAnimationFrameRequester = {
-                requestAnimationFrame: this._xrSession.requestAnimationFrame.bind(this._xrSession),
-                renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
-                    // Store the XR frame in the manager to be consumed by the XR camera to update pose
-                    this._currentXRFrame = xrFrame;
-                    this.onXRFrameObservable.notifyObservers(null);
-                    this.scene.getEngine()._renderLoop();
-                }
-            };
-            // Create render target texture from xr's webgl render target
-            this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this._xrSession, this.scene);
-
-            // Stop window's animation frame and trigger sessions animation frame
-            window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
-            this.scene.getEngine()._renderLoop();
+    /**
+     * Sets the reference space on the xr session
+     * @param referenceSpace space to set
+     * @returns a promise that will resolve once the reference space has been set
+     */
+    public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType) {
+        return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
+            this.referenceSpace = referenceSpace;
         });
     }
 
     /**
-     * Stops the xrSession and restores the renderloop
-     * @returns Promise which resolves after it exits XR
+     * Updates the render state of the session
+     * @param state state to set
+     * @returns a promise that resolves once the render state has been updated
      */
-    public exitXRAsync() {
-        return this._xrSession.end();
+    public updateRenderStateAsync(state: any) {
+        if (state.baseLayer) {
+            this.baseLayer = state.baseLayer;
+        }
+        return this.session.updateRenderState(state);
     }
 
     /**
-     * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
-     * @param ray ray to cast into the environment
-     * @returns Promise which resolves with a collision point in the environment if it exists
+     * Starts rendering to the xr layer
+     * @returns a promise that will resolve once rendering has started
      */
-    public environmentPointHitTestAsync(ray: Ray): Promise<Nullable<Vector3>> {
-        return new Promise((res) => {
-            // Compute left handed inputs to request hit test
-            var origin = new Float32Array([ray.origin.x, ray.origin.y, ray.origin.z]);
-            var direction = new Float32Array([ray.direction.x, ray.direction.y, ray.direction.z]);
-            if (!this.scene.useRightHandedSystem) {
-                origin[2] *= -1;
-                direction[2] *= -1;
+    public startRenderingToXRAsync() {
+        // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
+        this.scene.getEngine().customAnimationFrameRequester = {
+            requestAnimationFrame: this.session.requestAnimationFrame.bind(this.session),
+            renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
+                // Store the XR frame in the manager to be consumed by the XR camera to update pose
+                this.currentFrame = xrFrame;
+                this.onXRFrameObservable.notifyObservers(null);
+                this.scene.getEngine()._renderLoop();
             }
+        };
+        // Create render target texture from xr's webgl render target
+        this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!);
+
+        // Stop window's animation frame and trigger sessions animation frame
+        window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
+        this.scene.getEngine()._renderLoop();
+        return Promise.resolve();
+    }
 
-            // Fire hittest
-            this._xrSession.requestHitTest(origin, direction, this._frameOfReference)
-                .then((hits: any) => {
-                    if (hits.length > 0) {
-                        Matrix.FromFloat32ArrayToRefScaled(hits[0].hitMatrix, 0, 1.0, this._tmpMatrix);
-                        var hitPoint = this._tmpMatrix.getTranslation();
-                        if (!this.scene.useRightHandedSystem) {
-                            hitPoint.z *= -1;
-                        }
-                        res(hitPoint);
-                    } else {
-                        res(null);
-                    }
-                }).catch(() => {
-                    res(null);
-                });
-        });
+    /**
+     * Stops the xrSession and restores the renderloop
+     * @returns Promise which resolves after it exits XR
+     */
+    public exitXRAsync() {
+        if (this.session) {
+            this.session.end();
+        }
+        return new Promise(() => {});
     }
 
     /**
      * Checks if a session would be supported for the creation options specified
-     * @param options creation options to check if they are supported
+     * @param sessionMode session mode to check if supported eg. immersive-vr
      * @returns true if supported
      */
-    public supportsSessionAsync(options: XRSessionCreationOptions) {
-        return this._xrDevice.supportsSession(options).then(() => {
-            return true;
-        }).catch(() => {
-            return false;
-        });
+    public supportsSessionAsync(sessionMode: XRSessionMode) {
+        if (!(navigator as any).xr || !(navigator as any).xr.supportsSession) {
+            return Promise.resolve(false);
+        }else {
+            return (navigator as any).xr.supportsSession(sessionMode).then(() => {
+                return Promise.resolve(true);
+            }).catch((e: any) => {
+                Logger.Warn(e);
+                return Promise.resolve(false);
+            });
+        }
     }
 
     /**
@@ -168,12 +168,15 @@ export class WebXRSessionManager implements IDisposable {
      * @param session session to create render target for
      * @param scene scene the new render target should be created for
      */
-    public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene) {
+    public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
+        if (!baseLayer) {
+            throw "no layer";
+        }
         // Create internal texture
         var internalTexture = new InternalTexture(scene.getEngine(), InternalTexture.DATASOURCE_UNKNOWN, true);
-        internalTexture.width = session.baseLayer.framebufferWidth;
-        internalTexture.height = session.baseLayer.framebufferHeight;
-        internalTexture._framebuffer = session.baseLayer.framebuffer;
+        internalTexture.width = baseLayer.framebufferWidth;
+        internalTexture.height = baseLayer.framebufferHeight;
+        internalTexture._framebuffer = baseLayer.framebuffer;
 
         // Create render target texture from the internal texture
         var renderTargetTexture = new RenderTargetTexture("XR renderTargetTexture", { width: internalTexture.width, height: internalTexture.height }, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
@@ -189,4 +192,4 @@ export class WebXRSessionManager implements IDisposable {
         this.onXRFrameObservable.clear();
         this.onXRSessionEnded.clear();
     }
-}
+}

+ 1 - 1
src/Cameras/camera.ts

@@ -902,7 +902,7 @@ export class Camera extends Node {
     }
 
     /** @hidden */
-    public _isRightCamera = true;
+    public _isRightCamera = false;
     /**
      * Gets the right camera of a rig setup in case of Rigged Camera
      */

+ 33 - 3
src/Culling/ray.ts

@@ -245,6 +245,36 @@ export class Ray {
             return distance;
         }
     }
+    /**
+     * Calculate the intercept of a ray on a given axis
+     * @param axis to check 'x' | 'y' | 'z'
+     * @param offset from axis interception (i.e. an offset of 1y is intercepted above ground)
+     * @returns a vector containing the coordinates where 'axis' is equal to zero (else offset), or null if there is no intercept.
+     */
+    public intersectsAxis(axis: string, offset: number = 0): Nullable<Vector3> {
+        switch (axis) {
+            case 'y':
+                var t = (this.origin.y - offset) / this.direction.y;
+                if (t > 0) {
+                    return null;
+                }
+                return new Vector3(this.origin.x + (this.direction.x * -t), offset, this.origin.z + (this.direction.z * -t));
+            case 'x':
+                var t = (this.origin.x - offset) / this.direction.x;
+                if (t > 0) {
+                    return null;
+                }
+                return new Vector3(offset, this.origin.y + (this.direction.y * -t), this.origin.z + (this.direction.z * -t));
+            case 'z':
+                var t = (this.origin.z - offset) / this.direction.z;
+                if (t > 0) {
+                    return null;
+                }
+                return new Vector3(this.origin.x + (this.direction.x * -t), this.origin.y + (this.direction.y * -t), offset);
+            default:
+                return null;
+        }
+    }
 
     /**
      * Checks if ray intersects a mesh
@@ -666,8 +696,8 @@ Scene.prototype._internalPick = function(rayFunction: (world: Matrix) => Ray, pr
 };
 
 Scene.prototype._internalMultiPick = function(rayFunction: (world: Matrix) => Ray,
-        predicate?: (mesh: AbstractMesh) => boolean,
-        trianglePredicate?: TrianglePickingPredicate): Nullable<PickingInfo[]> {
+    predicate?: (mesh: AbstractMesh) => boolean,
+    trianglePredicate?: TrianglePickingPredicate): Nullable<PickingInfo[]> {
     if (!PickingInfo) {
         return null;
     }
@@ -775,4 +805,4 @@ Camera.prototype.getForwardRay = function(length = 100, transform?: Matrix, orig
     var direction = Vector3.Normalize(forwardWorld);
 
     return new Ray(origin, direction, length);
-};
+};

+ 10 - 2
src/Engines/engine.ts

@@ -500,14 +500,14 @@ export class Engine {
      */
     // Not mixed with Version for tooling purpose.
     public static get NpmPackage(): string {
-        return "babylonjs@4.1.0-alpha.6";
+        return "babylonjs@4.1.0-alpha.7";
     }
 
     /**
      * Returns the current version of the framework
      */
     public static get Version(): string {
-        return "4.1.0-alpha.6";
+        return "4.1.0-alpha.7";
     }
 
     /**
@@ -1564,6 +1564,14 @@ export class Engine {
     }
 
     /**
+     * Gets a string idenfifying the name of the class
+     * @returns "Engine" string
+     */
+    public getClassName(): string {
+        return "Engine";
+    }
+
+    /**
      * Returns true if the stencil buffer has been enabled through the creation option of the context.
      */
     public get isStencilEnable(): boolean {

+ 23 - 8
src/Gamepads/Controllers/poseEnabledController.ts

@@ -131,6 +131,10 @@ export class PoseEnabledControllerHelper {
  * Defines the PoseEnabledController object that contains state of a vr capable controller
  */
 export class PoseEnabledController extends Gamepad implements PoseControlled {
+    /**
+     * If the controller is used in a webXR session
+     */
+    public isXR = false;
     // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
     private _deviceRoomPosition = Vector3.Zero();
     private _deviceRoomRotationQuaternion = new Quaternion();
@@ -228,6 +232,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * Updates the state of the pose enbaled controller and mesh based on the current position and rotation of the controller
      */
     public update() {
+        if (this.isXR) {
+            return;
+        }
         super.update();
         this._updatePoseAndMesh();
     }
@@ -236,6 +243,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * Updates only the pose device and mesh without doing any button event checking
      */
     protected _updatePoseAndMesh() {
+        if (this.isXR) {
+            return;
+        }
         var pose: GamepadPose = this.browserGamepad.pose;
         this.updateFromDevice(pose);
 
@@ -283,6 +293,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * @param poseData raw pose fromthe device
      */
     updateFromDevice(poseData: DevicePose) {
+        if (this.isXR) {
+            return;
+        }
         if (poseData) {
             this.rawPose = poseData;
             if (poseData.position) {
@@ -335,15 +348,17 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
         }
 
         // Sync controller mesh and pointing pose node's state with controller, this is done to avoid a frame where position is 0,0,0 when attaching mesh
-        this._updatePoseAndMesh();
-        if (this._pointingPoseNode) {
-            var parents = [];
-            var obj: Node = this._pointingPoseNode;
-            while (obj.parent) {
-                parents.push(obj.parent);
-                obj = obj.parent;
+        if (!this.isXR) {
+            this._updatePoseAndMesh();
+            if (this._pointingPoseNode) {
+                var parents = [];
+                var obj: Node = this._pointingPoseNode;
+                while (obj.parent) {
+                    parents.push(obj.parent);
+                    obj = obj.parent;
+                }
+                parents.reverse().forEach((p) => { p.computeWorldMatrix(true); });
             }
-            parents.reverse().forEach((p) => { p.computeWorldMatrix(true); });
         }
 
         this._meshAttachedObservable.notifyObservers(mesh);

+ 8 - 15
src/Helpers/sceneHelpers.ts

@@ -12,16 +12,13 @@ import { IEnvironmentHelperOptions, EnvironmentHelper } from "./environmentHelpe
 import { FreeCamera } from "../Cameras/freeCamera";
 import { ArcRotateCamera } from "../Cameras/arcRotateCamera";
 import { TargetCamera } from "../Cameras/targetCamera";
-import { WebXRManagedOutputCanvas } from "../Cameras/XR/webXRManagedOutputCanvas";
-import { WebXRInput } from "../Cameras/XR/webXRInput";
-import { WebXREnterExitUI } from "../Cameras/XR/webXREnterExitUI";
-import { WebXRExperienceHelper } from "../Cameras/XR/webXRExperienceHelper";
 import { VRExperienceHelperOptions, VRExperienceHelper } from "../Cameras/VR/vrExperienceHelper";
 
 import "../Materials/Textures/Loaders/ddsTextureLoader";
 import "../Materials/Textures/Loaders/envTextureLoader";
 import "../Materials/Textures/Loaders/ktxTextureLoader";
 import "../Meshes/Builders/boxBuilder";
+import { WebXRDefaultExperience, WebXRDefaultExperienceOptions } from '../Cameras/XR/webXRDefaultExperience';
 
 /** @hidden */
 export var _forceSceneHelpersToBundle = true;
@@ -82,11 +79,12 @@ declare module "../scene" {
         createDefaultVRExperience(webVROptions?: VRExperienceHelperOptions): VRExperienceHelper;
 
         /**
-         * Creates a new XREXperienceHelper
+         * Creates a new WebXRDefaultExperience
          * @see http://doc.babylonjs.com/how_to/webxr
-         * @returns a promise for a new XREXperienceHelper
+         * @param options experience options
+         * @returns a promise for a new WebXRDefaultExperience
          */
-        createDefaultXRExperienceAsync(): Promise<WebXRExperienceHelper>;
+        createDefaultXRExperienceAsync(options: WebXRDefaultExperienceOptions): Promise<WebXRDefaultExperience>;
     }
 }
 
@@ -210,13 +208,8 @@ Scene.prototype.createDefaultVRExperience = function(webVROptions: VRExperienceH
     return new VRExperienceHelper(this, webVROptions);
 };
 
-Scene.prototype.createDefaultXRExperienceAsync = function(): Promise<WebXRExperienceHelper> {
-    return WebXRExperienceHelper.CreateAsync(this).then((helper) => {
-        var outputCanvas = new WebXRManagedOutputCanvas(helper);
-        return WebXREnterExitUI.CreateAsync(this, helper, { outputCanvasContext: outputCanvas.canvasContext })
-            .then((ui) => {
-                new WebXRInput(helper);
-                return helper;
-            });
+Scene.prototype.createDefaultXRExperienceAsync = function(options: WebXRDefaultExperienceOptions): Promise<WebXRDefaultExperience> {
+    return WebXRDefaultExperience.CreateAsync(this, options).then((helper) => {
+        return helper;
     });
 };

+ 24 - 4
src/Layers/effectLayer.ts

@@ -479,8 +479,16 @@ export abstract class EffectLayer {
                 attribs.push(VertexBuffer.MatricesIndicesExtraKind);
                 attribs.push(VertexBuffer.MatricesWeightsExtraKind);
             }
+
             defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
-            defines.push("#define BonesPerMesh " + (mesh.skeleton ? (mesh.skeleton.bones.length + 1) : 0));
+
+            let skeleton = mesh.skeleton;
+            if (skeleton && skeleton.isUsingTextureForMatrices) {
+                defines.push("#define BONETEXTURE");
+            } else {
+                defines.push("#define BonesPerMesh " + (skeleton ? (skeleton.bones.length + 1) : 0));
+            }
+
             if (mesh.numBoneInfluencers > 0) {
                 fallbacks.addCPUSkinningFallback(0, mesh);
             }
@@ -515,9 +523,9 @@ export abstract class EffectLayer {
             this._effectLayerMapGenerationEffect = this._scene.getEngine().createEffect("glowMapGeneration",
                 attribs,
                 ["world", "mBones", "viewProjection",
-                    "glowColor", "morphTargetInfluences",
+                    "glowColor", "morphTargetInfluences", "boneTextureWidth",
                     "diffuseMatrix", "emissiveMatrix", "opacityMatrix", "opacityIntensity"],
-                ["diffuseSampler", "emissiveSampler", "opacitySampler"], join,
+                ["diffuseSampler", "emissiveSampler", "opacitySampler", "boneSampler"], join,
                 fallbacks, undefined, undefined, { maxSimultaneousMorphTargets: morphInfluencers });
         }
 
@@ -713,7 +721,19 @@ export abstract class EffectLayer {
 
             // Bones
             if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
-                this._effectLayerMapGenerationEffect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
+                const skeleton = mesh.skeleton;
+
+                if (skeleton.isUsingTextureForMatrices) {
+                    const boneTexture = skeleton.getTransformMatrixTexture(mesh);
+                    if (!boneTexture) {
+                        return;
+                    }
+
+                    this._effectLayerMapGenerationEffect.setTexture("boneSampler", boneTexture);
+                    this._effectLayerMapGenerationEffect.setFloat("boneTextureWidth", 4.0 * (skeleton.bones.length + 1));
+                } else {
+                    this._effectLayerMapGenerationEffect.setMatrices("mBones", skeleton.getTransformMatrices((mesh)));
+                }
             }
 
             // Morph targets

+ 106 - 30
src/LibDeclarations/webxr.d.ts

@@ -1,41 +1,117 @@
-interface XRDevice {
-    requestSession(options: XRSessionCreationOptions): Promise<XRSession>;
-    supportsSession(options: XRSessionCreationOptions): Promise<void>;
+type XRSessionMode =
+    | "inline"
+    | "immersive-vr"
+    | "immersive-ar";
+
+type XRReferenceSpaceType =
+    | "viewer"
+    | "local"
+    | "local-floor"
+    | "bounded-floor"
+    | "unbounded";
+
+type XREnvironmentBlendMode =
+    | "opaque"
+    | "additive"
+    | "alpha-blend";
+
+type XRVisibilityState =
+    | "visible"
+    | "visible-blurred"
+    | "hidden";
+
+type XRHandedness =
+    | "none"
+    | "left"
+    | "right";
+
+type XRTargetRayMode =
+    | "gaze"
+    | "tracked-pointer"
+    | "screen";
+
+type XREye =
+    | "none"
+    | "left"
+    | "right";
+
+interface XRSpace extends EventTarget {
+
 }
-interface XRSession {
-    getInputSources(): Array<any>;
-    baseLayer: XRWebGLLayer;
-    requestFrameOfReference(type: string): Promise<void>;
-    requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
-    end(): Promise<void>;
-    requestAnimationFrame: Function;
-    addEventListener: Function;
+
+interface XRRenderState {
+    depthNear: number ;
+    depthFar: number ;
+    inlineVerticalFieldOfView: number | undefined;
+    baseLayer: XRWebGLLayer | undefined;
 }
-interface XRSessionCreationOptions {
-    outputContext?: WebGLRenderingContext | null;
-    immersive?: boolean;
-    environmentIntegration?: boolean;
+
+interface XRInputSource {
+    handedness: XRHandedness;
+    targetRayMode: XRTargetRayMode;
+    targetRaySpace: XRSpace;
+    gripSpace: XRSpace | undefined;
+    gamepad: Gamepad | undefined;
+    profiles: Array<string>;
 }
-interface XRLayer {
-    getViewport: Function;
-    framebufferWidth: number;
-    framebufferHeight: number;
+
+interface XRSession {
+    addEventListener: Function;
+    requestReferenceSpace(type: XRReferenceSpaceType): Promise<XRReferenceSpace>;
+    updateRenderState(XRRenderStateInit: any): Promise<void>;
+    requestAnimationFrame: Function;
+    end(): Promise<void>;
+    renderState: XRRenderState;
+    inputSources: Array<XRInputSource>;
+
 }
-interface XRView {
-    projectionMatrix: Float32Array;
+
+interface XRReferenceSpace extends XRSpace {
+    getOffsetReferenceSpace(originOffset: XRRigidTransform): XRReferenceSpace;
+    onreset: any;
 }
+
 interface XRFrame {
-    getDevicePose: Function;
-    getInputPose: Function;
-    views: Array<XRView>;
-    baseLayer: XRLayer;
+    session: XRSession;
+    getViewerPose(referenceSpace: XRReferenceSpace): XRViewerPose | undefined;
+    getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 }
-interface XRFrameOfReference {
+
+interface XRViewerPose extends XRPose {
+    views: Array<XRView>;
 }
-interface XRWebGLLayer extends XRLayer {
-    framebuffer: WebGLFramebuffer;
+
+interface XRPose {
+    transform: XRRigidTransform;
+    emulatedPosition: boolean;
 }
+
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context?: WebGLRenderingContext): XRWebGLLayer;
-};
+    new(session: XRSession, context: WebGLRenderingContext | undefined): XRWebGLLayer;
+};
+interface XRWebGLLayer {
+    framebuffer: WebGLFramebuffer;
+    framebufferWidth: number;
+    framebufferHeight: number;
+    getViewport: Function;
+}
+
+interface XRRigidTransform {
+    position: DOMPointReadOnly;
+    orientation: DOMPointReadOnly;
+    matrix: Float32Array;
+    inverse: XRRigidTransform;
+}
+
+interface XRView {
+    eye: XREye;
+    projectionMatrix: Float32Array;
+    transform: XRRigidTransform;
+}
+
+interface XRInputSourceChangeEvent {
+    session: XRSession;
+    removed: Array<XRInputSource>;
+    added: Array<XRInputSource>;
+}

+ 21 - 26
src/Materials/Node/Blocks/Dual/fogBlock.ts

@@ -9,11 +9,15 @@ import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPo
 import { AbstractMesh } from '../../../../Meshes/abstractMesh';
 import { MaterialHelper } from '../../../materialHelper';
 import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { InputBlock } from '../Input/inputBlock';
 
 /**
  * Block used to add support for scene fog
  */
 export class FogBlock extends NodeMaterialBlock {
+    private _fogDistanceName: string;
+    private _fogParameters: string;
+
     /**
      * Create a new FogBlock
      * @param name defines the block name
@@ -25,12 +29,9 @@ export class FogBlock extends NodeMaterialBlock {
         this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
         this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Vertex);
 
-        this.registerOutput("vFogDistance", NodeMaterialBlockConnectionPointTypes.Vector3, NodeMaterialBlockTargets.Vertex);
-
         // Fragment
         this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3OrColor4, false, NodeMaterialBlockTargets.Fragment);
         this.registerInput("fogColor", NodeMaterialBlockConnectionPointTypes.Color3, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("fogParameters", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Fragment);
 
         this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
     }
@@ -72,13 +73,6 @@ export class FogBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the for parameter input component
-     */
-    public get fogParameters(): NodeMaterialConnectionPoint {
-        return this._inputs[4];
-    }
-
-    /**
      * Gets the output component
      */
     public get output(): NodeMaterialConnectionPoint {
@@ -86,16 +80,16 @@ export class FogBlock extends NodeMaterialBlock {
     }
 
     public autoConfigure() {
-        if (this.view.isUndefined) {
-            this.view.setAsWellKnownValue(NodeMaterialWellKnownValues.View);
+        if (!this.view.isConnected) {
+            let viewInput = new InputBlock("view");
+            viewInput.setAsWellKnownValue(NodeMaterialWellKnownValues.View);
+            viewInput.output.connectTo(this.view);
         }
-        if (this.fogColor.isUndefined) {
-            this.fogColor.setAsWellKnownValue(NodeMaterialWellKnownValues.Automatic);
+        if (!this.fogColor.isConnected) {
+            let fogColorInput = new InputBlock("fogColor", undefined, NodeMaterialBlockConnectionPointTypes.Color3);
+            fogColorInput.setAsWellKnownValue(NodeMaterialWellKnownValues.FogColor);
+            fogColorInput.output.connectTo(this.fogColor);
         }
-        if (this.fogParameters.isUndefined) {
-            this.fogParameters.setAsWellKnownValue(NodeMaterialWellKnownValues.Automatic);
-        }
-        this._outputs[0].isVarying = true;
     }
 
     public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
@@ -109,8 +103,7 @@ export class FogBlock extends NodeMaterialBlock {
         }
 
         const scene = mesh.getScene();
-        effect.setColor3("u_fogColor", scene.fogColor);
-        effect.setFloat4("u_fogParameters", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
+        effect.setFloat4(this._fogParameters, scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {
@@ -131,20 +124,22 @@ export class FogBlock extends NodeMaterialBlock {
             let tempFogVariablename = state._getFreeVariableName("fog");
             let color = this.color;
             let fogColor = this.fogColor;
-            let fogParameters = this.fogParameters;
-            let output = this._outputs[1];
-            let vFogDistance = this._outputs[0];
+            this._fogParameters = state._getFreeVariableName("fogParameters");
+            let output = this._outputs[0];
+
+            state._emitUniformFromString(this._fogParameters, "vec4");
 
             state.compilationString += `#ifdef FOG\r\n`;
-            state.compilationString += `float ${tempFogVariablename} = CalcFogFactor(${vFogDistance.associatedVariableName}, ${fogParameters.associatedVariableName});\r\n`;
+            state.compilationString += `float ${tempFogVariablename} = CalcFogFactor(${this._fogDistanceName}, ${this._fogParameters});\r\n`;
             state.compilationString += this._declareOutput(output, state) + ` = ${tempFogVariablename} * ${color.associatedVariableName}.rgb + (1.0 - ${tempFogVariablename}) * ${fogColor.associatedVariableName};\r\n`;
             state.compilationString += `#else\r\n${this._declareOutput(output, state)} =  ${color.associatedVariableName}.rgb;\r\n`;
             state.compilationString += `#endif\r\n`;
         } else {
             let worldPos = this.worldPosition;
             let view = this.view;
-            let vFogDistance = this._outputs[0];
-            state.compilationString += this._declareOutput(vFogDistance, state) + ` = (${view.associatedVariableName} * ${worldPos.associatedVariableName}).xyz;\r\n`;
+            this._fogDistanceName = state._getFreeVariableName("vFogDistance");
+            state._emitVaryingFromString(this._fogDistanceName, "vec3");
+            state.compilationString += `${this._fogDistanceName} = (${view.associatedVariableName} * ${worldPos.associatedVariableName}).xyz;\r\n`;
         }
 
         return this;

+ 2 - 1
src/Materials/Node/Blocks/Dual/index.ts

@@ -1,3 +1,4 @@
 
 export * from "./fogBlock";
-export * from "./lightBlock";
+export * from "./lightBlock";
+export * from "./textureBlock";

+ 28 - 23
src/Materials/Node/Blocks/Dual/lightBlock.ts

@@ -9,6 +9,9 @@ import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
 import { Effect } from '../../../effect';
 import { Mesh } from '../../../../Meshes/mesh';
 import { NodeMaterialWellKnownValues } from '../../nodeMaterialWellKnownValues';
+import { InputBlock } from '../Input/inputBlock';
+import { Light } from '../../../../Lights/light';
+import { Nullable } from '../../../../types';
 
 /**
  * Block used to add light in the fragment shader
@@ -17,6 +20,11 @@ export class LightBlock extends NodeMaterialBlock {
     private _lightId: number;
 
     /**
+     * Gets or sets the light associated with this block
+     */
+    public light: Nullable<Light>;
+
+    /**
      * Create a new LightBlock
      * @param name defines the block name
      */
@@ -26,7 +34,6 @@ export class LightBlock extends NodeMaterialBlock {
         this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
         this.registerInput("worldNormal", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
 
-        this.registerInput("light", NodeMaterialBlockConnectionPointTypes.Light, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("cameraPosition", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("diffuseOutput", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("specularOutput", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
@@ -55,18 +62,10 @@ export class LightBlock extends NodeMaterialBlock {
     }
 
     /**
-    * Gets the light input component.
-    * If not defined, all lights will be considered
-    */
-    public get light(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
-    }
-
-    /**
     * Gets the camera (or eye) position component
     */
     public get cameraPosition(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
+        return this._inputs[2];
     }
 
     /**
@@ -84,15 +83,21 @@ export class LightBlock extends NodeMaterialBlock {
     }
 
     public autoConfigure() {
-        if (this.cameraPosition.isUndefined) {
-            this.cameraPosition.setAsWellKnownValue(NodeMaterialWellKnownValues.CameraPosition);
+        if (!this.cameraPosition.isConnected) {
+            let cameraPositionInput = new InputBlock("cameraPosition");
+            cameraPositionInput.setAsWellKnownValue(NodeMaterialWellKnownValues.CameraPosition);
+            cameraPositionInput.output.connectTo(this.cameraPosition);
         }
     }
 
     public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!defines._areLightsDirty) {
+            return;
+        }
+
         const scene = mesh.getScene();
 
-        if (!this.light.value) {
+        if (!this.light) {
             MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, true, nodeMaterial.maxSimultaneousLights);
         } else {
             let state = {
@@ -103,7 +108,7 @@ export class LightBlock extends NodeMaterialBlock {
                 specularEnabled: false
             };
 
-            MaterialHelper.PrepareDefinesForLight(scene, mesh, this.light.value, this._lightId, defines, true, state);
+            MaterialHelper.PrepareDefinesForLight(scene, mesh, this.light, this._lightId, defines, true, state);
 
             if (state.needRebuild) {
                 defines.rebuild();
@@ -127,10 +132,10 @@ export class LightBlock extends NodeMaterialBlock {
 
         const scene = mesh.getScene();
 
-        if (!this.light.value) {
+        if (!this.light) {
             MaterialHelper.BindLights(scene, mesh, effect, true, nodeMaterial.maxSimultaneousLights, false);
         } else {
-            MaterialHelper.BindLight(this.light.value, this._lightId, scene, mesh, effect, true, false);
+            MaterialHelper.BindLight(this.light, this._lightId, scene, mesh, effect, true, false);
         }
     }
 
@@ -140,10 +145,10 @@ export class LightBlock extends NodeMaterialBlock {
 
         // Inject code in vertex
         let worldPosVaryingName = "v_" + worldPos.associatedVariableName;
-        state._emitVaryings(worldPos, undefined, true, false, worldPosVaryingName, NodeMaterialBlockConnectionPointTypes.Vector3);
+        state._emitVaryingFromString(worldPosVaryingName, "vec3");
 
         let worldNormalVaryingName = "v_" + worldNormal.associatedVariableName;
-        state._emitVaryings(worldNormal, undefined, true, false, worldNormalVaryingName, NodeMaterialBlockConnectionPointTypes.Vector3);
+        state._emitVaryingFromString(worldNormalVaryingName, "vec3");
 
         state.compilationString += `${worldPosVaryingName} = ${worldPos.associatedVariableName}.xyz;\r\n`;
         state.compilationString += `${worldNormalVaryingName} = ${worldNormal.associatedVariableName}.xyz;\r\n`;
@@ -153,12 +158,12 @@ export class LightBlock extends NodeMaterialBlock {
         super._buildBlock(state);
 
         if (state.target !== NodeMaterialBlockTargets.Fragment) {
+            // Vertex
+            this._injectVertexCode(state);
+
             return;
         }
 
-        // Vertex
-        this._injectVertexCode(state._vertexState);
-
         // Fragment
         state.sharedData.bindableBlocks.push(this);
         state.sharedData.blocksWithDefines.push(this);
@@ -172,7 +177,7 @@ export class LightBlock extends NodeMaterialBlock {
             ]
         });
 
-        if (!this.light.value) { // Emit for all lights
+        if (!this.light) { // Emit for all lights
             state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, {
                 repeatKey: "maxSimultaneousLights"
             });
@@ -203,7 +208,7 @@ export class LightBlock extends NodeMaterialBlock {
             state.compilationString += `vec3 normalW = v_${this.worldNormal.associatedVariableName};\r\n`;
         }
 
-        if (this.light.value) {
+        if (this.light) {
             state.compilationString += state._emitCodeFromInclude("lightFragment", comments, {
                 replaceStrings: [
                     { search: /{X}/g, replace: this._lightId.toString() }

+ 185 - 0
src/Materials/Node/Blocks/Dual/textureBlock.ts

@@ -0,0 +1,185 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
+import { InputBlock } from '../Input/inputBlock';
+import { Effect } from '../../../effect';
+import { Mesh } from '../../../../Meshes/mesh';
+import { Nullable } from '../../../../types';
+
+/**
+ * Block used to read a texture from a sampler
+ */
+export class TextureBlock extends NodeMaterialBlock {
+    private _defineName: string;
+    private _samplerName: string;
+    private _transformedUVName: string;
+    private _textureTransformName: string;
+    private _textureInfoName: string;
+    private _mainUVName: string;
+    private _mainUVDefineName: string;
+
+    /**
+     * Gets or sets the texture associated with the node
+     */
+    public texture: Nullable<BaseTexture>;
+
+    /**
+     * Create a new TextureBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.VertexAndFragment);
+
+        this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, false, NodeMaterialBlockTargets.Vertex);
+
+        this.registerOutput("color", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "TextureBlock";
+    }
+
+    /**
+     * Gets the uv input component
+     */
+    public get uv(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the output component
+     */
+    public get output(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    public autoConfigure() {
+        if (!this.uv.isConnected) {
+            let uvInput = new InputBlock("uv");
+            uvInput.setAsAttribute();
+            uvInput.output.connectTo(this.uv);
+        }
+    }
+
+    public initializeDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances: boolean = false) {
+        if (!defines._areTexturesDirty) {
+            return;
+        }
+
+        defines.setValue(this._mainUVDefineName, false);
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        if (!defines._areTexturesDirty) {
+            return;
+        }
+
+        if (!this.texture || !this.texture.getTextureMatrix) {
+            return;
+        }
+
+        if (!this.texture.getTextureMatrix().isIdentityAs3x2()) {
+            defines.setValue(this._defineName, true);
+        } else {
+            defines.setValue(this._defineName, false);
+            defines.setValue(this._mainUVDefineName, true);
+        }
+    }
+
+    public isReady() {
+        if (this.texture && !this.texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        if (!mesh || !this.texture) {
+            return;
+        }
+
+        effect.setFloat(this._textureInfoName, this.texture.level);
+        effect.setMatrix(this._textureTransformName, this.texture.getTextureMatrix());
+        effect.setTexture(this._samplerName, this.texture);
+    }
+
+    private _injectVertexCode(state: NodeMaterialBuildState) {
+        let uvInput = this.uv;
+
+        // Inject code in vertex
+        this._defineName = state._getFreeDefineName("UVTRANSFORM");
+        this._mainUVDefineName = state._getFreeDefineName("vMain" + uvInput.associatedVariableName);
+
+        if (uvInput.connectedPoint!.ownerBlock.isInput) {
+            let uvInputOwnerBlock = uvInput.connectedPoint!.ownerBlock as InputBlock;
+
+            if (!uvInputOwnerBlock.isAttribute) {
+                state._emitUniformFromString(uvInput.associatedVariableName, "vec2");
+            }
+        }
+
+        this._mainUVName = "vMain" + uvInput.associatedVariableName;
+        this._transformedUVName = state._getFreeVariableName("transformedUV");
+        this._textureTransformName = state._getFreeVariableName("textureTransform");
+        this._textureInfoName = state._getFreeVariableName("textureInfoName");
+
+        state._emitVaryingFromString(this._transformedUVName, "vec2", this._defineName);
+        state._emitVaryingFromString(this._mainUVName, "vec2", this._mainUVDefineName);
+
+        state._emitUniformFromString(this._textureTransformName, "mat4", this._defineName);
+
+        if (state.sharedData.emitComments) {
+            state.compilationString += `\r\n//${this.name}\r\n`;
+        }
+        state.compilationString += `#ifdef ${this._defineName}\r\n`;
+        state.compilationString += `${this._transformedUVName} = vec2(${this._textureTransformName} * vec4(${uvInput.associatedVariableName}, 1.0, 0.0));\r\n`;
+        state.compilationString += `#else\r\n`;
+        state.compilationString += `${this._mainUVName} = ${uvInput.associatedVariableName};\r\n`;
+        state.compilationString += `#endif\r\n`;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target !== NodeMaterialBlockTargets.Fragment) {
+            // Vertex
+            this._injectVertexCode(state);
+            return;
+        }
+
+        state.sharedData.blockingBlocks.push(this);
+        state.sharedData.textureBlocks.push(this);
+
+        this._samplerName = state._getFreeVariableName(this.name + "Sampler");
+        state.samplers.push(this._samplerName);
+        state._samplerDeclaration += `uniform sampler2D ${this._samplerName};\r\n`;
+
+        // Fragment
+        state.sharedData.blocksWithDefines.push(this);
+        state.sharedData.bindableBlocks.push(this);
+
+        state._emitUniformFromString(this._textureInfoName, "float");
+
+        let uvInput = this.uv;
+        let output = this._outputs[0];
+        const complement = ` * ${this._textureInfoName}`;
+
+        state.compilationString += `#ifdef ${this._defineName}\r\n`;
+        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${this._samplerName}, ${this._transformedUVName})${complement};\r\n`;
+        state.compilationString += `#else\r\n`;
+        state.compilationString += `vec4 ${output.associatedVariableName} = texture2D(${this._samplerName}, ${"vMain" + uvInput.associatedVariableName})${complement};\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        return this;
+    }
+}

+ 4 - 2
src/Materials/Node/Blocks/Fragment/fragmentOutputBlock.ts

@@ -19,7 +19,7 @@ export class FragmentOutputBlock extends NodeMaterialBlock {
     public constructor(name: string) {
         super(name, NodeMaterialBlockTargets.Fragment, true);
 
-        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3OrColor4);
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Vector2OrVector3OrColor3OrVector4OrColor4);
     }
 
     /**
@@ -43,7 +43,9 @@ export class FragmentOutputBlock extends NodeMaterialBlock {
         let input = this.color;
         state.sharedData.hints.needAlphaBlending = this.alphaBlendingEnabled;
 
-        if (input.connectedPoint && input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color3) {
+        if (input.connectedPoint && input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Vector2) {
+            state.compilationString += `gl_FragColor = vec4(${input.associatedVariableName}.r, ${input.associatedVariableName}.g, 0., 1.0);\r\n`;
+        } else if (input.connectedPoint && (input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Color3 || input.connectedPoint!.type === NodeMaterialBlockConnectionPointTypes.Vector3)) {
             state.compilationString += `gl_FragColor = vec4(${input.associatedVariableName}, 1.0);\r\n`;
         } else {
             state.compilationString += `gl_FragColor = ${input.associatedVariableName};\r\n`;

+ 0 - 0
src/Materials/Node/Blocks/Fragment/index.ts


部分文件因文件數量過多而無法顯示