Ver código fonte

Merge pull request #9216 from BabylonJS/master

4.2.0-beta.14
mergify[bot] 4 anos atrás
pai
commit
a786fca559
77 arquivos alterados com 117579 adições e 115605 exclusões
  1. 20999 20859
      dist/preview release/babylon.d.ts
  2. 2 2
      dist/preview release/babylon.js
  3. 1920 1476
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 35729 35439
      dist/preview release/babylon.module.d.ts
  6. 20973 20833
      dist/preview release/documentation.d.ts
  7. 1 1
      dist/preview release/glTF2Interface/package.json
  8. 48 48
      dist/preview release/gui/babylon.gui.js
  9. 1 1
      dist/preview release/gui/babylon.gui.js.map
  10. 2 2
      dist/preview release/gui/package.json
  11. 7 7
      dist/preview release/inspector/package.json
  12. 3 3
      dist/preview release/loaders/package.json
  13. 2 2
      dist/preview release/materialsLibrary/package.json
  14. 11 1
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  15. 7 7
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  16. 65 16
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  17. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  18. 24 4
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  19. 2 2
      dist/preview release/nodeEditor/package.json
  20. 1 1
      dist/preview release/package.json
  21. 1 1
      dist/preview release/packagesSizeBaseLine.json
  22. 2 2
      dist/preview release/postProcessesLibrary/package.json
  23. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  24. 3 3
      dist/preview release/serializers/package.json
  25. 35729 35439
      dist/preview release/viewer/babylon.module.d.ts
  26. 49 45
      dist/preview release/viewer/babylon.viewer.js
  27. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  28. 3 1
      dist/preview release/what's new.md
  29. 1 1
      nodeEditor/public/index.js
  30. 0 6
      nodeEditor/src/blockTools.ts
  31. 1 3
      nodeEditor/src/components/nodeList/nodeListComponent.tsx
  32. 1 0
      nodeEditor/src/components/preview/previewManager.ts
  33. 24 0
      nodeEditor/src/diagram/display/pbrDisplayManager.ts
  34. 1 1
      nodeEditor/src/diagram/display/textureDisplayManager.ts
  35. 6 0
      nodeEditor/src/diagram/displayLedger.ts
  36. 4 2
      nodeEditor/src/diagram/graphFrame.ts
  37. 1 1
      nodeEditor/src/diagram/nodePort.ts
  38. 21 6
      nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx
  39. 1 1
      package.json
  40. 6 6
      src/Engines/thinEngine.ts
  41. 59 26
      src/Gizmos/axisDragGizmo.ts
  42. 100 33
      src/Gizmos/axisScaleGizmo.ts
  43. 89 0
      src/Gizmos/gizmo.ts
  44. 36 8
      src/Gizmos/gizmoManager.ts
  45. 38 28
      src/Gizmos/planeDragGizmo.ts
  46. 184 37
      src/Gizmos/planeRotationGizmo.ts
  47. 28 3
      src/Gizmos/positionGizmo.ts
  48. 28 4
      src/Gizmos/rotationGizmo.ts
  49. 77 18
      src/Gizmos/scaleGizmo.ts
  50. 1 1
      src/Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.ts
  51. 0 146
      src/Materials/Node/Blocks/PBR/ambientOcclusionBlock.ts
  52. 4 13
      src/Materials/Node/Blocks/PBR/anisotropyBlock.ts
  53. 48 26
      src/Materials/Node/Blocks/PBR/clearCoatBlock.ts
  54. 0 2
      src/Materials/Node/Blocks/PBR/index.ts
  55. 201 117
      src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts
  56. 12 4
      src/Materials/Node/Blocks/PBR/reflectionBlock.ts
  57. 0 243
      src/Materials/Node/Blocks/PBR/reflectivityBlock.ts
  58. 16 13
      src/Materials/Node/Blocks/PBR/refractionBlock.ts
  59. 20 48
      src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts
  60. 1 1
      src/Materials/Node/nodeMaterialBlock.ts
  61. 2 1
      src/Materials/PBR/pbrClearCoatConfiguration.ts
  62. 22 154
      src/Materials/Textures/baseTexture.ts
  63. 1 1
      src/Materials/Textures/cubeTexture.ts
  64. 286 0
      src/Materials/Textures/thinTexture.ts
  65. 3 3
      src/Materials/effect.ts
  66. 3 1
      src/Meshes/abstractMesh.ts
  67. 9 9
      src/Misc/screenshotTools.ts
  68. 16 1
      src/Rendering/edgesRenderer.ts
  69. 1 1
      src/Shaders/ShadersInclude/pbrBlockFinalLitComponents.fx
  70. 23 115
      src/Sprites/sprite.ts
  71. 78 314
      src/Sprites/spriteManager.ts
  72. 396 0
      src/Sprites/spriteRenderer.ts
  73. 140 0
      src/Sprites/thinSprite.ts
  74. 0 6
      src/assetContainer.ts
  75. BIN
      tests/validation/ReferenceImages/ktx2decoder.png
  76. BIN
      tests/validation/ReferenceImages/node-material-pbr-1.png
  77. 1 1
      tests/validation/config.json

Diferenças do arquivo suprimidas por serem muito extensas
+ 20999 - 20859
dist/preview release/babylon.d.ts


Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 2
dist/preview release/babylon.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1920 - 1476
dist/preview release/babylon.max.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
dist/preview release/babylon.max.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 35729 - 35439
dist/preview release/babylon.module.d.ts


Diferenças do arquivo suprimidas por serem muito extensas
+ 20973 - 20833
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.2.0-beta.13",
+    "version": "4.2.0-beta.14",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 48 - 48
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_perfCounter__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -400,7 +400,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_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -543,7 +543,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/perfCounter");
+/* 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___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");
@@ -1532,7 +1532,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _textBlock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textBlock */ "./2D/controls/textBlock.ts");
 /* harmony import */ var _image__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./image */ "./2D/controls/image.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__);
 
 
@@ -1764,7 +1764,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__["_TypeStore"].RegisteredTy
 __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/perfCounter");
+/* 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___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");
@@ -1947,7 +1947,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __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/perfCounter");
+/* 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___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");
@@ -3340,7 +3340,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __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/perfCounter");
+/* 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___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");
@@ -3757,7 +3757,7 @@ babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredTypes
 "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/perfCounter");
+/* 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___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");
@@ -5706,7 +5706,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DisplayGrid", function() { return DisplayGrid; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -5939,7 +5939,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -6036,7 +6036,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/perfCounter");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__);
 
 
@@ -6494,7 +6494,7 @@ babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__["_TypeStore"].RegisteredTypes[
 __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/perfCounter");
+/* 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___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");
 
@@ -7431,7 +7431,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputPassword", function() { return InputPassword; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -7470,7 +7470,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __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/perfCounter");
+/* 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___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");
@@ -8483,7 +8483,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __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_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__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");
@@ -8754,7 +8754,7 @@ babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registere
 __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/perfCounter");
+/* 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___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");
@@ -9024,7 +9024,7 @@ babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registe
 __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/perfCounter");
+/* 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___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");
@@ -9231,7 +9231,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Rectangle", function() { return Rectangle; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -9381,7 +9381,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _scrollViewerWindow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./scrollViewerWindow */ "./2D/controls/scrollViewers/scrollViewerWindow.ts");
 /* harmony import */ var _sliders_scrollBar__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../sliders/scrollBar */ "./2D/controls/sliders/scrollBar.ts");
 /* harmony import */ var _sliders_imageScrollBar__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../sliders/imageScrollBar */ "./2D/controls/sliders/imageScrollBar.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__);
 
 
@@ -11012,7 +11012,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/perfCounter");
+/* 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___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");
@@ -11346,7 +11346,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../measure */ "./2D/measure.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -11939,7 +11939,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Slider", function() { return Slider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -12211,7 +12211,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __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/perfCounter");
+/* 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___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");
@@ -12481,7 +12481,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/perfCounter");
+/* 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___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");
@@ -13017,7 +13017,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/perfCounter");
+/* 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___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");
@@ -13406,7 +13406,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_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -13631,7 +13631,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_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__);
 
 var tmpRect = [
@@ -13796,7 +13796,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_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -13939,7 +13939,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/perfCounter");
+/* 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___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");
 
@@ -14245,7 +14245,7 @@ var ValueAndUnit = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "XmlLoader", function() { return XmlLoader; });
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -14564,7 +14564,7 @@ var XmlLoader = /** @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/perfCounter");
+/* 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___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");
 
@@ -14607,7 +14607,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_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__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");
@@ -14788,7 +14788,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/perfCounter");
+/* 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___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");
 
@@ -14945,7 +14945,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/perfCounter");
+/* 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___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");
 
@@ -15351,7 +15351,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/perfCounter");
+/* 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___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");
@@ -15437,7 +15437,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_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Materials/standardMaterial */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Materials/standardMaterial */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_standardMaterial__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");
@@ -15931,7 +15931,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_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__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");
@@ -15986,7 +15986,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/perfCounter");
+/* 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___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");
@@ -16113,7 +16113,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/perfCounter");
+/* 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___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");
@@ -16199,7 +16199,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/perfCounter");
+/* 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___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");
 
@@ -16324,7 +16324,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/perfCounter");
+/* 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___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");
 
@@ -16515,7 +16515,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/perfCounter");
+/* 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___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");
 
@@ -16782,7 +16782,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/perfCounter");
+/* 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___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");
@@ -17098,7 +17098,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/perfCounter");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentPixelShader';
@@ -17120,7 +17120,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/perfCounter");
+/* 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___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentVertexShader';
@@ -17143,7 +17143,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_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -17445,14 +17445,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/perfCounter":
+/***/ "babylonjs/Misc/observable":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_perfCounter__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
 
 /***/ })
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 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.2.0-beta.13",
+    "version": "4.2.0-beta.14",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,7 +28,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.2.0-beta.13"
+        "babylonjs": "4.2.0-beta.14"
     },
     "engines": {
         "node": "*"

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

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

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 11 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts


Diferenças do arquivo suprimidas por serem muito extensas
+ 7 - 7
dist/preview release/nodeEditor/babylon.nodeEditor.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 65 - 16
dist/preview release/nodeEditor/babylon.nodeEditor.max.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 24 - 4
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts


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

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

@@ -1 +1 @@
-{"thinEngineOnly":119155,"engineOnly":155595,"sceneOnly":522958,"minGridMaterial":672170,"minStandardMaterial":829302}
+{"thinEngineOnly":119155,"engineOnly":155595,"sceneOnly":522960,"minGridMaterial":672172,"minStandardMaterial":830801}

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

Diferenças do arquivo suprimidas por serem muito extensas
+ 35729 - 35439
dist/preview release/viewer/babylon.module.d.ts


Diferenças do arquivo suprimidas por serem muito extensas
+ 49 - 45
dist/preview release/viewer/babylon.viewer.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -23,7 +23,8 @@
 - Refactored React refs from old string API to React.createRef() API ([belfortk](https://github.com/belfortk))
 - Scale on one axis for `BoundingBoxGizmo` ([cedricguillemet](https://github.com/cedricguillemet))
 - Camera gizmo ([cedricguillemet](https://github.com/cedricguillemet))
-- gizmo isHovered boolean ([cedricguillemet](https://github.com/cedricguillemet))
+- Upgraded gizmo meshes ([davesauce14](https://github.com/davesauce14))
+- Gizmo isHovered boolean ([cedricguillemet](https://github.com/cedricguillemet))
 - Node support (Transform, Bone) for gizmos ([cedricguillemet](https://github.com/cedricguillemet))
 - Simplified code contributions by fully automating the dev setup with gitpod ([nisarhassan12](https://github.com/nisarhassan12))
 - Add a `CascadedShadowMap.IsSupported` method and log an error instead of throwing an exception when CSM is not supported ([Popov72](https://github.com/Popov72))
@@ -355,6 +356,7 @@
 - Fix incorrect shadows on the master mesh when using a lod mesh ([Popov72](https://github.com/Popov72))
 - Take first gamepad connected when attaching camera (and not only XBOX gamepads) ([#9136](https://github.com/BabylonJS/Babylon.js/issues/9136)) ([RaananW](https://github.com/RaananW))
 - Fixed bug in `Mesh.IncreaseVertices` assuming null value if a property didn't exist. ([aWeirdo](https://github.com/aWeirdo))
+- Fix issue when taking a screenshot with multi-cameras using method `CreateScreenshotUsingRenderTarget` ([#9201](https://github.com/BabylonJS/Babylon.js/issues/9201)) ([gabrielheming](https://github.com/gabrielheming))
 
 ## Breaking changes
 

+ 1 - 1
nodeEditor/public/index.js

@@ -113,7 +113,7 @@ var showEditor = function() {
 // Let's start
 if (BABYLON.Engine.isSupported()) {
     var canvas = document.createElement("canvas");
-    var engine = new BABYLON.Engine(canvas, false, {disableWebGL2Support: true});
+    var engine = new BABYLON.Engine(canvas, false, {disableWebGL2Support: false});
     var scene = new BABYLON.Scene(engine);    
     var light0 = new BABYLON.HemisphericLight("light #0", new BABYLON.Vector3(0, 1, 0), scene);
     var light1 = new BABYLON.HemisphericLight("light #1", new BABYLON.Vector3(0, 1, 0), scene);

+ 0 - 6
nodeEditor/src/blockTools.ts

@@ -65,8 +65,6 @@ import { ReflectBlock } from 'babylonjs/Materials/Node/Blocks/reflectBlock';
 import { DesaturateBlock } from 'babylonjs/Materials/Node/Blocks/desaturateBlock';
 import { PBRMetallicRoughnessBlock } from 'babylonjs/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock';
 import { SheenBlock } from 'babylonjs/Materials/Node/Blocks/PBR/sheenBlock';
-import { AmbientOcclusionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/ambientOcclusionBlock';
-import { ReflectivityBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectivityBlock';
 import { AnisotropyBlock } from 'babylonjs/Materials/Node/Blocks/PBR/anisotropyBlock';
 import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionBlock';
 import { ClearCoatBlock } from 'babylonjs/Materials/Node/Blocks/PBR/clearCoatBlock';
@@ -460,10 +458,6 @@ export class BlockTools {
                 return new PBRMetallicRoughnessBlock("PBRMetallicRoughness");
             case "SheenBlock":
                 return new SheenBlock("Sheen");
-            case "AmbientOcclusionBlock":
-                return new AmbientOcclusionBlock("AmbientOcclusion");
-            case "ReflectivityBlock":
-                return new ReflectivityBlock("Reflectivity");
             case "AnisotropyBlock":
                 return new AnisotropyBlock("Anisotropy");
             case "ReflectionBlock":

+ 1 - 3
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -130,8 +130,6 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "Rotate2dBlock": "Rotates UV coordinates around the W axis.",
         "PBRMetallicRoughnessBlock": "PBR metallic/roughness material",
         "SheenBlock": "PBR Sheen block",
-        "AmbientOcclusionBlock": "PBR Ambient occlusion block",
-        "ReflectivityBlock": "PBR Reflectivity block",
         "AnisotropyBlock": "PBR Anisotropy block",
         "ReflectionBlock": "PBR Reflection block",
         "ClearCoatBlock": "PBR ClearCoat block",
@@ -238,7 +236,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
             Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticlePositionWorldBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
-            PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
+            PBR: ["PBRMetallicRoughnessBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             PostProcess: ["ScreenPositionBlock", "CurrentScreenBlock"],
             Procedural__Texture: ["ScreenPositionBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],

+ 1 - 0
nodeEditor/src/components/preview/previewManager.ts

@@ -99,6 +99,7 @@ export class PreviewManager {
         this._engine = new Engine(targetCanvas, true);
         this._scene = new Scene(this._engine);
         this._scene.clearColor = this._globalState.backgroundColor;
+        this._scene.ambientColor = new Color3(1, 1, 1);
         this._camera = new ArcRotateCamera("Camera", 0, 0.8, 4, Vector3.Zero(), this._scene);
 
         this._camera.lowerRadiusLimit = 3;

+ 24 - 0
nodeEditor/src/diagram/display/pbrDisplayManager.ts

@@ -0,0 +1,24 @@
+import { IDisplayManager } from './displayManager';
+import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
+
+export class PBRDisplayManager implements IDisplayManager {
+    public getHeaderClass(block: NodeMaterialBlock) {
+        return "";
+    }
+
+    public shouldDisplayPortLabels(block: NodeMaterialBlock): boolean {
+        return true;
+    }
+
+    public getHeaderText(block: NodeMaterialBlock): string {
+        return block.name;
+    }
+
+    public getBackgroundColor(block: NodeMaterialBlock): string {
+        return "#6174FA";
+    }
+
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {
+        contentArea.classList.add("pbr-block");
+    }
+}

+ 1 - 1
nodeEditor/src/diagram/display/textureDisplayManager.ts

@@ -24,7 +24,7 @@ export class TextureDisplayManager implements IDisplayManager {
     }
 
     public getBackgroundColor(block: NodeMaterialBlock): string {
-        return "#323232";
+        return block.getClassName() === "RefractionBlock" || block.getClassName() === "ReflectionBlock" ? "#6174FA" : "#323232";
     }
 
     public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {

+ 6 - 0
nodeEditor/src/diagram/displayLedger.ts

@@ -6,6 +6,7 @@ import { RemapDisplayManager } from './display/remapDisplayManager';
 import { TrigonometryDisplayManager } from './display/trigonometryDisplayManager';
 import { TextureDisplayManager } from './display/textureDisplayManager';
 import { DiscardDisplayManager } from './display/discardDisplayManager';
+import { PBRDisplayManager } from './display/pbrDisplayManager';
 
 export class DisplayLedger {
     public static RegisteredControls: {[key: string] : any} = {};
@@ -25,3 +26,8 @@ DisplayLedger.RegisteredControls["RefractionBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["CurrentScreenBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["ParticleTextureBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["DiscardBlock"] = DiscardDisplayManager;
+DisplayLedger.RegisteredControls["PBRMetallicRoughnessBlock"] = PBRDisplayManager;
+DisplayLedger.RegisteredControls["AnisotropyBlock"] = PBRDisplayManager;
+DisplayLedger.RegisteredControls["ClearCoatBlock"] = PBRDisplayManager;
+DisplayLedger.RegisteredControls["SheenBlock"] = PBRDisplayManager;
+DisplayLedger.RegisteredControls["SubSurfaceBlock"] = PBRDisplayManager;

+ 4 - 2
nodeEditor/src/diagram/graphFrame.ts

@@ -156,7 +156,7 @@ export class GraphFrame {
 
         for (var i = 0; i < this._exposedInPorts.length; ) { // Input
             var port = this._exposedInPorts[i];
-            if(port.node === null || port.node.enclosingFrameId != this.id) {
+            if(!port || port.node === null || port.node.enclosingFrameId != this.id) {
                 if(this.removePortFromExposedWithNode(port, this._exposedInPorts)) {
                     continue;
                 }
@@ -194,7 +194,9 @@ export class GraphFrame {
         let index = exposedPorts.findIndex(nodePort => nodePort === port);
         if(index >= 0) {
             exposedPorts.splice(index,1)
-            port.exposedPortPosition = -1;
+            if (port) {
+                port.exposedPortPosition = -1;
+            }
             return true;
         }
         return false;

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
nodeEditor/src/diagram/nodePort.ts


+ 21 - 6
nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx

@@ -26,7 +26,7 @@ type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock | RefractionBl
 
 type AnyTexture = TextureBlock | ReflectionTexture | CurrentScreenBlock | ParticleTextureBlock;
 
-export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
+export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean, textureIsPrefiltered: boolean}> {
 
     get textureBlock(): AnyTexture {
         return this.props.block as AnyTexture;
@@ -37,7 +37,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
         let texture = this.textureBlock.texture as BaseTexture;
 
-        this.state = {isEmbedded: !texture || texture.name.substring(0, 4) === "data", loadAsCubeTexture: texture && texture.isCube};
+        this.state = {isEmbedded: !texture || texture.name.substring(0, 4) === "data", loadAsCubeTexture: texture && texture.isCube, textureIsPrefiltered: true};
     }
 
     UNSAFE_componentWillUpdate(nextProps: IPropertyComponentProps, nextState: {isEmbedded: boolean, loadAsCubeTexture: boolean}) {
@@ -109,7 +109,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
             var blob = new Blob([data], { type: "octet/stream" });
 
             var reader = new FileReader();
-            reader.readAsDataURL(blob); 
+            reader.readAsDataURL(blob);
             reader.onloadend = () => {
                 let base64data = reader.result as string;                
 
@@ -119,8 +119,11 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                 } else if (file.name.toLowerCase().indexOf(".env") > 0) {
                     extension = ".env";
                 }
-
-                (texture as Texture).updateURL(base64data, extension, () => this.updateAfterTextureLoad());
+                if (texture.isCube) {
+                    (texture as CubeTexture).updateURL(base64data, extension, () => this.updateAfterTextureLoad(), this.state.textureIsPrefiltered);
+                } else {
+                    (texture as Texture).updateURL(base64data, extension, () => this.updateAfterTextureLoad());
+                }
             }
         }, undefined, true);
     }
@@ -137,7 +140,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                 extension = ".env";
             }
 
-            (texture as Texture).updateURL(url, extension, () => this.updateAfterTextureLoad());
+            (texture as CubeTexture).updateURL(url, extension, () => this.updateAfterTextureLoad(), this.state.textureIsPrefiltered);
         } else {
             (texture as Texture).updateURL(url, null, () => this.updateAfterTextureLoad());
         }
@@ -155,6 +158,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
 
         let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock;
         let isFrozenTexture = this.textureBlock instanceof CurrentScreenBlock || this.textureBlock instanceof ParticleTextureBlock;
+        let showIsInGammaSpace = this.textureBlock instanceof ReflectionBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {
@@ -206,6 +210,12 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                         }}/>
                     }
                     {
+                        texture && showIsInGammaSpace &&
+                        <CheckBoxLineComponent label="Is in gamma space" propertyName="gammaSpace" target={texture} onValueChanged={() => {                        
+                            this.props.globalState.onUpdateRequiredObservable.notifyObservers();
+                        }}/>
+                    }
+                    {
                         texture && isInReflectionMode &&
                         <OptionsLineComponent label="Reflection mode" options={reflectionModeOptions} target={texture} propertyName="coordinatesMode" onSelect={(value: any) => {
                             texture.coordinatesMode = value;
@@ -294,6 +304,11 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
                             onSelect={value => this.setState({loadAsCubeTexture: value})}/> 
                     }
                     {
+                        isInReflectionMode && this.state.loadAsCubeTexture &&
+                        <CheckBoxLineComponent label="&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Texture is prefiltered" isSelected={() => this.state.textureIsPrefiltered} 
+                            onSelect={value => this.setState({textureIsPrefiltered: value})}/> 
+                    }
+                    {
                         this.state.isEmbedded &&
                         <FileButtonLineComponent label="Upload" onClick={(file) => this.replaceTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
                     }

+ 1 - 1
package.json

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

+ 6 - 6
src/Engines/thinEngine.ts

@@ -24,7 +24,7 @@ import { IPipelineContext } from './IPipelineContext';
 import { WebGLPipelineContext } from './WebGL/webGLPipelineContext';
 import { VertexBuffer } from '../Meshes/buffer';
 import { InstancingAttributeInfo } from './instancingAttributeInfo';
-import { BaseTexture } from '../Materials/Textures/baseTexture';
+import { ThinTexture } from '../Materials/Textures/thinTexture';
 import { IOfflineProvider } from '../Offline/IOfflineProvider';
 import { IEffectFallbacks } from '../Materials/iEffectFallbacks';
 import { IWebRequest } from '../Misc/interfaces/iWebRequest';
@@ -158,14 +158,14 @@ export class ThinEngine {
      */
     // Not mixed with Version for tooling purpose.
     public static get NpmPackage(): string {
-        return "babylonjs@4.2.0-beta.13";
+        return "babylonjs@4.2.0-beta.14";
     }
 
     /**
      * Returns the current version of the framework
      */
     public static get Version(): string {
-        return "4.2.0-beta.13";
+        return "4.2.0-beta.14";
     }
 
     /**
@@ -3616,7 +3616,7 @@ export class ThinEngine {
      * @param uniform The uniform to set
      * @param texture The texture to apply
      */
-    public setTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<BaseTexture>): void {
+    public setTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<ThinTexture>): void {
         if (channel === undefined) {
             return;
         }
@@ -3649,7 +3649,7 @@ export class ThinEngine {
         return this._gl.REPEAT;
     }
 
-    protected _setTexture(channel: number, texture: Nullable<BaseTexture>, isPartOfTextureArray = false, depthStencilTexture = false): boolean {
+    protected _setTexture(channel: number, texture: Nullable<ThinTexture>, isPartOfTextureArray = false, depthStencilTexture = false): boolean {
         // Not ready?
         if (!texture) {
             if (this._boundTexturesCache[channel] != null) {
@@ -3749,7 +3749,7 @@ export class ThinEngine {
      * @param uniform defines the associated uniform location
      * @param textures defines the array of textures to bind
      */
-    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: BaseTexture[]): void {
+    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: ThinTexture[]): void {
         if (channel === undefined || !uniform) {
             return;
         }

+ 59 - 26
src/Gizmos/axisDragGizmo.ts

@@ -8,7 +8,7 @@ import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
 import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { StandardMaterial } from "../Materials/standardMaterial";
 import { Scene } from "../scene";
@@ -36,25 +36,33 @@ export class AxisDragGizmo extends Gizmo {
     private _isEnabled: boolean = true;
     private _parent: Nullable<PositionGizmo> = null;
 
-    private _arrow: TransformNode;
+    private _gizmoMesh: Mesh;
     private _coloredMaterial: StandardMaterial;
     private _hoverMaterial: StandardMaterial;
+    private _disableMaterial: StandardMaterial;
+    private _dragging: boolean = false;
 
     /** @hidden */
-    public static _CreateArrow(scene: Scene, material: StandardMaterial, thickness: number = 1): TransformNode {
+    public static _CreateArrow(scene: Scene, material: StandardMaterial, thickness: number = 1, isCollider = false): TransformNode {
         var arrow = new TransformNode("arrow", scene);
         var cylinder = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0, height: 0.075, diameterBottom: 0.0375 * (1 + (thickness - 1) / 4), tessellation: 96 }, scene);
         var line = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0.005 * thickness, height: 0.275, diameterBottom: 0.005 * thickness, tessellation: 96 }, scene);
-        line.material = material;
-        cylinder.parent = arrow;
-        line.parent = arrow;
 
         // Position arrow pointing in its drag axis
+        cylinder.parent = arrow;
         cylinder.material = material;
         cylinder.rotation.x = Math.PI / 2;
         cylinder.position.z += 0.3;
+
+        line.parent = arrow;
+        line.material = material;
         line.position.z += 0.275 / 2;
         line.rotation.x = Math.PI / 2;
+
+        if (isCollider) {
+            line.visibility = 0;
+            cylinder.visibility = 0;
+        }
         return arrow;
     }
 
@@ -78,20 +86,31 @@ export class AxisDragGizmo extends Gizmo {
     constructor(dragAxis: Vector3, color: Color3 = Color3.Gray(), gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, parent: Nullable<PositionGizmo> = null, thickness: number = 1) {
         super(gizmoLayer);
         this._parent = parent;
+
         // Create Material
         this._coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
         this._coloredMaterial.diffuseColor = color;
         this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
 
         this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        this._hoverMaterial.diffuseColor = color.add(new Color3(0.3, 0.3, 0.3));
+        this._hoverMaterial.diffuseColor = Color3.Yellow();
+
+        this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._disableMaterial.diffuseColor = Color3.Gray();
+        this._disableMaterial.alpha = 0.4;
 
-        // Build mesh on root node
-        this._arrow = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness);
+        // Build Mesh + Collider
+        const arrow = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness);
+        const collider = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness + 4, true);
 
-        this._arrow.lookAt(this._rootMesh.position.add(dragAxis));
-        this._arrow.scaling.scaleInPlace(1 / 3);
-        this._arrow.parent = this._rootMesh;
+        // Add to Root Node
+        this._gizmoMesh = new Mesh("", gizmoLayer.utilityLayerScene);
+        this._gizmoMesh.addChild((arrow as Mesh));
+        this._gizmoMesh.addChild((collider as Mesh));
+
+        this._gizmoMesh.lookAt(this._rootMesh.position.add(dragAxis));
+        this._gizmoMesh.scaling.scaleInPlace(1 / 3);
+        this._gizmoMesh.parent = this._rootMesh;
 
         var currentSnapDragDistance = 0;
         var tmpVector = new Vector3();
@@ -132,23 +151,37 @@ export class AxisDragGizmo extends Gizmo {
                 this._matrixChanged();
             }
         });
+        this.dragBehavior.onDragStartObservable.add(() => { this._dragging = true; });
+        this.dragBehavior.onDragEndObservable.add(() => { this._dragging = false; });
+
+        var light = gizmoLayer._getSharedGizmoLight();
+        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
+
+        const cache: GizmoAxisCache = {
+            gizmoMeshes: arrow.getChildMeshes() as Mesh[],
+            colliderMeshes: collider.getChildMeshes() as Mesh[],
+            material: this._coloredMaterial,
+            hoverMaterial: this._hoverMaterial,
+            disableMaterial: this._disableMaterial,
+            active: false
+        };
+        this._parent?.addToAxisCache(collider as Mesh, cache);
 
         this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
             if (this._customMeshSet) {
                 return;
             }
-            this._isHovered = !!(pointerInfo.pickInfo && (this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1));
-            var material = this._isHovered ? this._hoverMaterial : this._coloredMaterial;
-            this._rootMesh.getChildMeshes().forEach((m) => {
-                m.material = material;
-                if ((<LinesMesh>m).color) {
-                    (<LinesMesh>m).color = material.diffuseColor;
-                }
-            });
+            this._isHovered = !!(cache.colliderMeshes.indexOf(<Mesh>pointerInfo?.pickInfo?.pickedMesh) != -1);
+            if (!this._parent) {
+                var material = this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial;
+                cache.gizmoMeshes.forEach((m: Mesh) => {
+                    m.material = material;
+                    if ((<LinesMesh>m).color) {
+                        (<LinesMesh>m).color = material.diffuseColor;
+                    }
+                });
+            }
         });
-
-        var light = gizmoLayer._getSharedGizmoLight();
-        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
     }
     protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
@@ -183,10 +216,10 @@ export class AxisDragGizmo extends Gizmo {
         this.onSnapObservable.clear();
         this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
         this.dragBehavior.detach();
-        if (this._arrow) {
-            this._arrow.dispose();
+        if (this._gizmoMesh) {
+            this._gizmoMesh.dispose();
         }
-        [this._coloredMaterial, this._hoverMaterial].forEach((matl) => {
+        [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
             if (matl) {
                 matl.dispose();
             }

+ 100 - 33
src/Gizmos/axisScaleGizmo.ts

@@ -10,7 +10,7 @@ import { BoxBuilder } from "../Meshes/Builders/boxBuilder";
 import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder";
 import { StandardMaterial } from "../Materials/standardMaterial";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { ScaleGizmo } from "./scaleGizmo";
 import { Color3 } from '../Maths/math.color';
@@ -45,9 +45,11 @@ export class AxisScaleGizmo extends Gizmo {
     private _isEnabled: boolean = true;
     private _parent: Nullable<ScaleGizmo> = null;
 
-    private _arrow: AbstractMesh;
+    private _gizmoMesh: Mesh;
     private _coloredMaterial: StandardMaterial;
     private _hoverMaterial: StandardMaterial;
+    private _disableMaterial: StandardMaterial;
+    private _dragging: boolean = false;
 
     /**
      * Creates an AxisScaleGizmo
@@ -65,26 +67,45 @@ export class AxisScaleGizmo extends Gizmo {
         this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
 
         this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        this._hoverMaterial.diffuseColor = color.add(new Color3(0.3, 0.3, 0.3));
+        this._hoverMaterial.diffuseColor = Color3.Yellow();
 
-        // Build mesh on root node
-        this._arrow = new AbstractMesh("", gizmoLayer.utilityLayerScene);
-        var arrowMesh = BoxBuilder.CreateBox("yPosMesh", { size: 0.4 * (1 + (thickness - 1) / 4) }, gizmoLayer.utilityLayerScene);
-        var arrowTail = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0.005 * thickness, height: 0.275, diameterBottom: 0.005 * thickness, tessellation: 96 }, gizmoLayer.utilityLayerScene);
-        arrowTail.material = this._coloredMaterial;
-        this._arrow.addChild(arrowMesh);
-        this._arrow.addChild(arrowTail);
+        this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._disableMaterial.diffuseColor = Color3.Gray();
+        this._disableMaterial.alpha = 0.4;
 
-        // Position arrow pointing in its drag axis
-        arrowMesh.scaling.scaleInPlace(0.1);
-        arrowMesh.material = this._coloredMaterial;
-        arrowMesh.rotation.x = Math.PI / 2;
-        arrowMesh.position.z += 0.3;
-        arrowTail.position.z += 0.275 / 2;
-        arrowTail.rotation.x = Math.PI / 2;
-        this._arrow.lookAt(this._rootMesh.position.add(dragAxis));
-        this._rootMesh.addChild(this._arrow);
-        this._arrow.scaling.scaleInPlace(1 / 3);
+        // Build mesh + Collider
+        this._gizmoMesh = new Mesh("axis", gizmoLayer.utilityLayerScene);
+        const { arrowMesh, arrowTail } = this._createGizmoMesh(this._gizmoMesh, thickness);
+        const collider = this._createGizmoMesh(this._gizmoMesh, thickness + 4, true);
+
+        this._gizmoMesh.lookAt(this._rootMesh.position.add(dragAxis));
+        this._rootMesh.addChild(this._gizmoMesh);
+        this._gizmoMesh.scaling.scaleInPlace(1 / 3);
+
+        // Closure of inital prop values for resetting
+        const nodePosition = arrowMesh.position.clone();
+        const linePosition = arrowTail.position.clone();
+        const lineScale = arrowTail.scaling.clone();
+
+        const increaseGizmoMesh = (dragDistance: number) => {
+            const dragStrength = this.sensitivity * dragDistance * ((this.scaleRatio * 3) / this._rootMesh.scaling.length());
+            const scalar = 1; // This will increase the rate of gizmo size on drag
+            const originalScale = arrowTail.scaling.y;
+            const newScale = originalScale + dragStrength * scalar;
+            const newMeshPosition = arrowMesh.position.z + ((newScale - originalScale) / 4);
+            if (newMeshPosition >= 0) {
+                arrowMesh.position.z = newMeshPosition;
+                arrowTail.scaling.y = newScale;
+                arrowTail.position.z = arrowMesh.position.z / 2;
+            }
+        };
+
+        const resetGizmoMesh = () => {
+            arrowMesh.position.set(nodePosition.x, nodePosition.y, nodePosition.z);
+            arrowTail.position.set(linePosition.x, linePosition.y, linePosition.z);
+            arrowTail.scaling.set(lineScale.x, lineScale.y, lineScale.z);
+            this._dragging = false;
+        };
 
         // Add drag behavior to handle events when the gizmo is dragged
         this.dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
@@ -139,25 +160,71 @@ export class AxisScaleGizmo extends Gizmo {
                 this._matrixChanged();
             }
         });
+        // On Drag Listener: to move gizmo mesh with user action
+        this.dragBehavior.onDragStartObservable.add(() => { this._dragging = true; });
+        this.dragBehavior.onDragObservable.add((e) => increaseGizmoMesh(e.dragDistance));
+        this.dragBehavior.onDragEndObservable.add(resetGizmoMesh);
+
+        // Listeners for Universal Scalar
+        parent?.uniformScaleGizmo?.dragBehavior?.onDragObservable?.add((e) => increaseGizmoMesh(e.delta.y));
+        parent?.uniformScaleGizmo?.dragBehavior?.onDragEndObservable?.add(resetGizmoMesh);
+
+        const cache: GizmoAxisCache = {
+            gizmoMeshes: [arrowMesh, arrowTail],
+            colliderMeshes: [collider.arrowMesh, collider.arrowTail],
+            material: this._coloredMaterial,
+            hoverMaterial: this._hoverMaterial,
+            disableMaterial: this._disableMaterial,
+            active: false
+        };
+        this._parent?.addToAxisCache(this._gizmoMesh, cache);
 
         this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
             if (this._customMeshSet) {
                 return;
             }
-            this._isHovered = !!(pointerInfo.pickInfo && (this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1));
-            var material = this._isHovered ? this._hoverMaterial : this._coloredMaterial;
-            this._rootMesh.getChildMeshes().forEach((m) => {
-                m.material = material;
-                if ((<LinesMesh>m).color) {
-                    (<LinesMesh>m).color = material.diffuseColor;
-                }
-            });
+            this._isHovered = !!(cache.colliderMeshes.indexOf(<Mesh>pointerInfo?.pickInfo?.pickedMesh) != -1);
+            if (!this._parent) {
+                var material = this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial;
+                cache.gizmoMeshes.forEach((m: Mesh) => {
+                    m.material = material;
+                    if ((<LinesMesh>m).color) {
+                        (<LinesMesh>m).color = material.diffuseColor;
+                    }
+                });
+            }
         });
 
         var light = gizmoLayer._getSharedGizmoLight();
         light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes());
     }
 
+    /** Create Geometry for Gizmo */
+    private _createGizmoMesh(parentMesh: AbstractMesh, thickness: number, isCollider = false) {
+        var arrowMesh = BoxBuilder.CreateBox("yPosMesh", { size: 0.4 * (1 + (thickness - 1) / 4) }, this.gizmoLayer.utilityLayerScene);
+        var arrowTail = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0.005 * thickness, height: 0.275, diameterBottom: 0.005 * thickness, tessellation: 96 }, this.gizmoLayer.utilityLayerScene);
+
+        // Position arrow pointing in its drag axis
+        arrowMesh.scaling.scaleInPlace(0.1);
+        arrowMesh.material = this._coloredMaterial;
+        arrowMesh.rotation.x = Math.PI / 2;
+        arrowMesh.position.z += 0.3;
+
+        arrowTail.material = this._coloredMaterial;
+        arrowTail.position.z += 0.275 / 2;
+        arrowTail.rotation.x = Math.PI / 2;
+
+        if (isCollider) {
+            arrowMesh.visibility = 0;
+            arrowTail.visibility = 0;
+        }
+
+        parentMesh.addChild(arrowMesh);
+        parentMesh.addChild(arrowTail);
+
+        return { arrowMesh, arrowTail };
+    }
+
     protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
             this.dragBehavior.enabled = value ? true : false;
@@ -165,8 +232,8 @@ export class AxisScaleGizmo extends Gizmo {
     }
 
     /**
- * If the gizmo is enabled
- */
+     * If the gizmo is enabled
+     */
     public set isEnabled(value: boolean) {
         this._isEnabled = value;
         if (!value) {
@@ -191,10 +258,10 @@ export class AxisScaleGizmo extends Gizmo {
         this.onSnapObservable.clear();
         this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
         this.dragBehavior.detach();
-        if (this._arrow) {
-            this._arrow.dispose();
+        if (this._gizmoMesh) {
+            this._gizmoMesh.dispose();
         }
-        [this._coloredMaterial, this._hoverMaterial].forEach((matl) => {
+        [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
             if (matl) {
                 matl.dispose();
             }

+ 89 - 0
src/Gizmos/gizmo.ts

@@ -11,6 +11,27 @@ import { Node } from "../node";
 import { Bone } from "../Bones/bone";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { TransformNode } from '../Meshes/transformNode';
+import { StandardMaterial } from '../Materials/standardMaterial';
+import { PointerEventTypes, PointerInfo } from '../Events/pointerEvents';
+import { LinesMesh } from '../Meshes/linesMesh';
+
+/**
+ * Cache built by each axis. Used for managing state between all elements of gizmo for enhanced UI
+ */
+export interface GizmoAxisCache {
+    /** Mesh used to runder the Gizmo */
+    gizmoMeshes: Mesh[];
+    /** Mesh used to detect user interaction with Gizmo */
+    colliderMeshes: Mesh[];
+    /** Material used to inicate color of gizmo mesh */
+    material: StandardMaterial;
+    /** Material used to inicate hover state of the Gizmo */
+    hoverMaterial: StandardMaterial;
+    /** Material used to inicate disabled state of the Gizmo */
+    disableMaterial: StandardMaterial;
+    /** Used to indicate Active state of the Gizmo */
+    active: boolean;
+}
 /**
  * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
  */
@@ -276,6 +297,74 @@ export class Gizmo implements IDisposable {
     }
 
     /**
+     * Subscribes to pointer up, down, and hover events. Used for responsive gizmos.
+     * @param gizmoLayer The utility layer the gizmo will be added to
+     * @param gizmoAxisCache Gizmo axis definition used for reactive gizmo UI
+     * @returns {Observer<PointerInfo>} pointerObserver
+     */
+    public static GizmoAxisPointerObserver(gizmoLayer: UtilityLayerRenderer, gizmoAxisCache: Map<Mesh, GizmoAxisCache>): Observer<PointerInfo> {
+
+        let dragging = false;
+
+        const pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
+            if (pointerInfo.pickInfo) {
+                // On Hover Logic
+                if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
+                    if (dragging) { return; }
+                    gizmoAxisCache.forEach((cache) => {
+                        if (cache.colliderMeshes && cache.gizmoMeshes) {
+                            const isHovered = (cache.colliderMeshes?.indexOf((pointerInfo?.pickInfo?.pickedMesh as Mesh)) != -1);
+                            const material = isHovered || cache.active ? cache.hoverMaterial : cache.material;
+                            cache.gizmoMeshes.forEach((m: Mesh) => {
+                                m.material = material;
+                                if ((m as LinesMesh).color) {
+                                    (m as LinesMesh).color = material.diffuseColor;
+                                }
+                            });
+                        }
+                    });
+                }
+
+                // On Mouse Down
+                if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
+                    // If user Clicked Gizmo
+                    if (gizmoAxisCache.has(pointerInfo.pickInfo.pickedMesh?.parent as Mesh)) {
+                        dragging = true;
+                        const statusMap = gizmoAxisCache.get(pointerInfo.pickInfo.pickedMesh?.parent as Mesh);
+                        statusMap!.active = true;
+                        gizmoAxisCache.forEach((cache) => {
+                            const isHovered = (cache.colliderMeshes?.indexOf((pointerInfo?.pickInfo?.pickedMesh as Mesh)) != -1);
+                            const material = isHovered || cache.active ? cache.hoverMaterial : cache.disableMaterial;
+                            cache.gizmoMeshes.forEach((m: Mesh) => {
+                                m.material = material;
+                                if ((m as LinesMesh).color) {
+                                    (m as LinesMesh).color = material.diffuseColor;
+                                }
+                            });
+                        });
+                    }
+                }
+
+                // On Mouse Up
+                if (pointerInfo.type === PointerEventTypes.POINTERUP) {
+                    gizmoAxisCache.forEach((cache) => {
+                        cache.active = false;
+                        dragging = false;
+                        cache.gizmoMeshes.forEach((m: Mesh) => {
+                            m.material = cache.material;
+                            if ((m as LinesMesh).color) {
+                                (m as LinesMesh).color = cache.material.diffuseColor;
+                            }
+                        });
+                    });
+                }
+            }
+        });
+
+        return pointerObserver!;
+    }
+
+    /**
      * Disposes of the gizmo
      */
     public dispose() {

+ 36 - 8
src/Gizmos/gizmoManager.ts

@@ -4,11 +4,11 @@ import { PointerInfo, PointerEventTypes } from "../Events/pointerEvents";
 import { Scene, IDisposable } from "../scene";
 import { Node } from "../node";
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Mesh } from '../Meshes/mesh';
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { Color3 } from '../Maths/math.color';
 import { SixDofDragBehavior } from "../Behaviors/Meshes/sixDofDragBehavior";
-
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { RotationGizmo } from "./rotationGizmo";
 import { PositionGizmo } from "./positionGizmo";
 import { ScaleGizmo } from "./scaleGizmo";
@@ -33,13 +33,15 @@ export class GizmoManager implements IDisposable {
     public onAttachedToNodeObservable = new Observable<Nullable<Node>>();
 
     private _gizmosEnabled = { positionGizmo: false, rotationGizmo: false, scaleGizmo: false, boundingBoxGizmo: false };
-    private _pointerObserver: Nullable<Observer<PointerInfo>> = null;
+    private _pointerObservers: Observer<PointerInfo>[] = [];
     private _attachedMesh: Nullable<AbstractMesh> = null;
     private _attachedNode: Nullable<Node> = null;
     private _boundingBoxColor = Color3.FromHexString("#0984e3");
     private _defaultUtilityLayer: UtilityLayerRenderer;
     private _defaultKeepDepthUtilityLayer: UtilityLayerRenderer;
     private _thickness: number = 1;
+    /** Node Caching for quick lookup */
+    private _gizmoAxisCache: Map<Mesh, GizmoAxisCache> = new Map();
     /**
      * When bounding box gizmo is enabled, this can be used to track drag/end events
      */
@@ -98,8 +100,18 @@ export class GizmoManager implements IDisposable {
         this._thickness = thickness;
         this.gizmos = { positionGizmo: null, rotationGizmo: null, scaleGizmo: null, boundingBoxGizmo: null };
 
+        const attachToMeshPointerObserver = this._attachToMeshPointerObserver(scene);
+        const gizmoAxisPointerObserver = Gizmo.GizmoAxisPointerObserver(this._defaultUtilityLayer, this._gizmoAxisCache);
+        this._pointerObservers = [attachToMeshPointerObserver, gizmoAxisPointerObserver];
+    }
+
+    /**
+     * Subscribes to pointer down events, for attaching and detaching mesh
+     * @param scene The sceme layer the observer will be added to
+     */
+    private _attachToMeshPointerObserver(scene: Scene): Observer<PointerInfo> {
         // Instatiate/dispose gizmos based on pointer actions
-        this._pointerObserver = scene.onPointerObservable.add((pointerInfo) => {
+        const pointerObserver = scene.onPointerObservable.add((pointerInfo) => {
             if (!this.usePointerToAttachGizmos) {
                 return;
             }
@@ -140,6 +152,7 @@ export class GizmoManager implements IDisposable {
                 }
             }
         });
+        return pointerObserver!;
     }
 
     /**
@@ -198,7 +211,7 @@ export class GizmoManager implements IDisposable {
     public set positionGizmoEnabled(value: boolean) {
         if (value) {
             if (!this.gizmos.positionGizmo) {
-                this.gizmos.positionGizmo = new PositionGizmo(this._defaultUtilityLayer, this._thickness);
+                this.gizmos.positionGizmo = new PositionGizmo(this._defaultUtilityLayer, this._thickness, this);
             }
             if (this._attachedNode) {
                 this.gizmos.positionGizmo.attachedNode = this._attachedNode;
@@ -219,7 +232,7 @@ export class GizmoManager implements IDisposable {
     public set rotationGizmoEnabled(value: boolean) {
         if (value) {
             if (!this.gizmos.rotationGizmo) {
-                this.gizmos.rotationGizmo = new RotationGizmo(this._defaultUtilityLayer, 32, false, this._thickness);
+                this.gizmos.rotationGizmo = new RotationGizmo(this._defaultUtilityLayer, 32, false, this._thickness, this);
             }
             if (this._attachedNode) {
                 this.gizmos.rotationGizmo.attachedNode = this._attachedNode;
@@ -239,7 +252,7 @@ export class GizmoManager implements IDisposable {
      */
     public set scaleGizmoEnabled(value: boolean) {
         if (value) {
-            this.gizmos.scaleGizmo = this.gizmos.scaleGizmo || new ScaleGizmo(this._defaultUtilityLayer, this._thickness);
+            this.gizmos.scaleGizmo = this.gizmos.scaleGizmo || new ScaleGizmo(this._defaultUtilityLayer, this._thickness, this);
             if (this._attachedNode) {
                 this.gizmos.scaleGizmo.attachedNode = this._attachedNode;
             } else {
@@ -287,10 +300,25 @@ export class GizmoManager implements IDisposable {
     }
 
     /**
+     * Builds Gizmo Axis Cache to enable features such as hover state preservation and graying out other axis during manipulation
+     * @param gizmoAxisCache Gizmo axis definition used for reactive gizmo UI
+     */
+    public addToAxisCache(gizmoAxisCache: Map<Mesh, GizmoAxisCache>) {
+        if (gizmoAxisCache.size > 0) {
+            gizmoAxisCache.forEach((v, k) => {
+                this._gizmoAxisCache.set(k, v);
+            });
+        }
+    }
+
+    /**
      * Disposes of the gizmo manager
      */
     public dispose() {
-        this.scene.onPointerObservable.remove(this._pointerObserver);
+
+        this._pointerObservers.forEach((observer) => {
+            this.scene.onPointerObservable.remove(observer);
+        });
         for (var key in this.gizmos) {
             var gizmo = <Nullable<Gizmo>>((<any>this.gizmos)[key]);
             if (gizmo) {

+ 38 - 28
src/Gizmos/planeDragGizmo.ts

@@ -8,7 +8,7 @@ import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
 import { PlaneBuilder } from "../Meshes/Builders/planeBuilder";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { StandardMaterial } from "../Materials/standardMaterial";
 import { Scene } from "../scene";
@@ -32,12 +32,14 @@ export class PlaneDragGizmo extends Gizmo {
      */
     public onSnapObservable = new Observable<{ snapDistance: number }>();
 
-    private _plane: TransformNode;
+    private _gizmoMesh: TransformNode;
     private _coloredMaterial: StandardMaterial;
     private _hoverMaterial: StandardMaterial;
+    private _disableMaterial: StandardMaterial;
 
     private _isEnabled: boolean = false;
     private _parent: Nullable<PositionGizmo> = null;
+    private _dragging: boolean = false;
 
     /** @hidden */
     public static _CreatePlane(scene: Scene, material: StandardMaterial): TransformNode {
@@ -50,16 +52,6 @@ export class PlaneDragGizmo extends Gizmo {
         return plane;
     }
 
-    /** @hidden */
-    public static _CreateArrowInstance(scene: Scene, arrow: TransformNode): TransformNode {
-        const instance = new TransformNode("arrow", scene);
-        for (const mesh of arrow.getChildMeshes()) {
-            const childInstance = (mesh as Mesh).createInstance(mesh.name);
-            childInstance.parent = instance;
-        }
-        return instance;
-    }
-
     /**
      * Creates a PlaneDragGizmo
      * @param gizmoLayer The utility layer the gizmo will be added to
@@ -75,14 +67,18 @@ export class PlaneDragGizmo extends Gizmo {
         this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
 
         this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        this._hoverMaterial.diffuseColor = color.add(new Color3(0.3, 0.3, 0.3));
+        this._hoverMaterial.diffuseColor = Color3.Yellow();
+
+        this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._disableMaterial.diffuseColor = Color3.Gray();
+        this._disableMaterial.alpha = 0.4;
 
         // Build plane mesh on root node
-        this._plane = PlaneDragGizmo._CreatePlane(gizmoLayer.utilityLayerScene, this._coloredMaterial);
+        this._gizmoMesh = PlaneDragGizmo._CreatePlane(gizmoLayer.utilityLayerScene, this._coloredMaterial);
 
-        this._plane.lookAt(this._rootMesh.position.add(dragPlaneNormal));
-        this._plane.scaling.scaleInPlace(1 / 3);
-        this._plane.parent = this._rootMesh;
+        this._gizmoMesh.lookAt(this._rootMesh.position.add(dragPlaneNormal));
+        this._gizmoMesh.scaling.scaleInPlace(1 / 3);
+        this._gizmoMesh.parent = this._rootMesh;
 
         var currentSnapDragDistance = 0;
         var tmpVector = new Vector3();
@@ -116,20 +112,34 @@ export class PlaneDragGizmo extends Gizmo {
                 this._matrixChanged();
             }
         });
+        this.dragBehavior.onDragStartObservable.add(() => { this._dragging = true; });
+        this.dragBehavior.onDragEndObservable.add(() => { this._dragging = false; });
+
+        var light = gizmoLayer._getSharedGizmoLight();
+        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
+
+        const cache: GizmoAxisCache = {
+            gizmoMeshes: this._gizmoMesh.getChildMeshes() as Mesh[],
+            colliderMeshes: this._gizmoMesh.getChildMeshes() as Mesh[],
+            material: this._coloredMaterial,
+            hoverMaterial: this._hoverMaterial,
+            disableMaterial: this._disableMaterial,
+            active: false
+        };
+        this._parent?.addToAxisCache((this._gizmoMesh as Mesh), cache);
 
         this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
             if (this._customMeshSet) {
                 return;
             }
-            this._isHovered = !!(pointerInfo.pickInfo && (this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1));
-            var material = this._isHovered ? this._hoverMaterial : this._coloredMaterial;
-            this._rootMesh.getChildMeshes().forEach((m) => {
-                m.material = material;
-            });
+            this._isHovered = !!(cache.colliderMeshes.indexOf(<Mesh>pointerInfo?.pickInfo?.pickedMesh) != -1);
+            if (!this._parent) {
+                var material = this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial;
+                cache.gizmoMeshes.forEach((m: Mesh) => {
+                    m.material = material;
+                });
+            }
         });
-
-        var light = gizmoLayer._getSharedGizmoLight();
-        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
     }
     protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
@@ -162,10 +172,10 @@ export class PlaneDragGizmo extends Gizmo {
         this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
         this.dragBehavior.detach();
         super.dispose();
-        if (this._plane) {
-            this._plane.dispose();
+        if (this._gizmoMesh) {
+            this._gizmoMesh.dispose();
         }
-        [this._coloredMaterial, this._hoverMaterial].forEach((matl) => {
+        [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
             if (matl) {
                 matl.dispose();
             }

+ 184 - 37
src/Gizmos/planeRotationGizmo.ts

@@ -3,16 +3,15 @@ import { Nullable } from "../types";
 import { PointerInfo } from "../Events/pointerEvents";
 import { Quaternion, Matrix, Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
+import "../Meshes/Builders/linesBuilder";
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { LinesMesh } from '../Meshes/linesMesh';
 import { Mesh } from "../Meshes/mesh";
 import { Node } from "../node";
-import { LinesMesh } from "../Meshes/linesMesh";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { StandardMaterial } from "../Materials/standardMaterial";
-
-import "../Meshes/Builders/linesBuilder";
 import { RotationGizmo } from "./rotationGizmo";
 
 /**
@@ -37,6 +36,19 @@ export class PlaneRotationGizmo extends Gizmo {
 
     private _isEnabled: boolean = true;
     private _parent: Nullable<RotationGizmo> = null;
+    private _coloredMaterial: StandardMaterial;
+    private _hoverMaterial: StandardMaterial;
+    private _disableMaterial: StandardMaterial;
+    private _gizmoMesh: Mesh;
+    private _rotationCircle: Mesh;
+    private _dragging: boolean = false;
+
+    private static _CircleConstants = {
+        radius: 0.3,
+        pi2: Math.PI * 2,
+        tessellation: 70,
+        rotationCircleRange: 4
+    };
 
     /**
      * Creates a PlaneRotationGizmo
@@ -51,30 +63,28 @@ export class PlaneRotationGizmo extends Gizmo {
         super(gizmoLayer);
         this._parent = parent;
         // Create Material
-        var coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        coloredMaterial.diffuseColor = color;
-        coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
+        this._coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._coloredMaterial.diffuseColor = color;
+        this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
 
-        var hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
-        hoverMaterial.diffuseColor = color.add(new Color3(0.3, 0.3, 0.3));
+        this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._hoverMaterial.diffuseColor = Color3.Yellow();
 
-        // Build mesh on root node
-        var parentMesh = new AbstractMesh("", gizmoLayer.utilityLayerScene);
+        this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
+        this._disableMaterial.diffuseColor = Color3.Gray();
+        this._disableMaterial.alpha = 0.4;
 
-        let drag = Mesh.CreateTorus("", 0.6, 0.03 * thickness, tessellation, gizmoLayer.utilityLayerScene);
-        drag.visibility = 0;
-        let rotationMesh = Mesh.CreateTorus("", 0.6, 0.005 * thickness, tessellation, gizmoLayer.utilityLayerScene);
-        rotationMesh.material = coloredMaterial;
+        // Build mesh on root node
+        this._gizmoMesh = new Mesh("", gizmoLayer.utilityLayerScene);
+        const { rotationMesh, collider } = this._createGizmoMesh(this._gizmoMesh, thickness, tessellation);
 
-        // Position arrow pointing in its drag axis
-        rotationMesh.rotation.x = Math.PI / 2;
-        drag.rotation.x = Math.PI / 2;
-        parentMesh.addChild(rotationMesh);
-        parentMesh.addChild(drag);
-        parentMesh.lookAt(this._rootMesh.position.add(planeNormal));
+        // Setup Rotation Circle
+        const rotationCirclePaths: any[] = [];
+        this._rotationCircle = this.setupRotationCircle(rotationCirclePaths, this._gizmoMesh);
 
-        this._rootMesh.addChild(parentMesh);
-        parentMesh.scaling.scaleInPlace(1 / 3);
+        this._gizmoMesh.lookAt(this._rootMesh.position.add(planeNormal));
+        this._rootMesh.addChild(this._gizmoMesh);
+        this._gizmoMesh.scaling.scaleInPlace(1 / 3);
         // Add drag behavior to handle events when the gizmo is dragged
         this.dragBehavior = new PointerDragBehavior({ dragPlaneNormal: planeNormal });
         this.dragBehavior.moveAttached = false;
@@ -82,17 +92,44 @@ export class PlaneRotationGizmo extends Gizmo {
         this.dragBehavior._useAlternatePickedPointAboveMaxDragAngle = true;
         this._rootMesh.addBehavior(this.dragBehavior);
 
-        var lastDragPosition = new Vector3();
+        // Closures for drag logic
+        let dragDistance = 0;
+        const lastDragPosition = new Vector3();
+        let dragPlanePoint = new Vector3();
+        const rotationMatrix = new Matrix();
+        const planeNormalTowardsCamera = new Vector3();
+        let localPlaneNormalTowardsCamera = new Vector3();
 
         this.dragBehavior.onDragStartObservable.add((e) => {
             if (this.attachedNode) {
                 lastDragPosition.copyFrom(e.dragPlanePoint);
+
+                // This is for instantiation location of rotation circle
+                const forward = new Vector3(0, 0, 1);
+                const direction = this._rotationCircle.getDirection(forward);
+                direction.normalize();
+
+                // Remove Rotation Circle from parent mesh before drag interaction
+                this._gizmoMesh.removeChild(this._rotationCircle);
+
+                lastDragPosition.copyFrom(e.dragPlanePoint);
+                dragPlanePoint = e.dragPlanePoint;
+                const origin = this._rotationCircle.getAbsolutePosition().clone();
+                const originalRotationPoint = this._rotationCircle.getAbsolutePosition().clone().addInPlace(direction);
+                const dragStartPoint = e.dragPlanePoint;
+                const angle = Vector3.GetAngleBetweenVectors(originalRotationPoint.subtract(origin), dragStartPoint.subtract(origin), this._rotationCircle.up);
+
+                this._rotationCircle.addRotation(0, angle, 0);
+                this._dragging = true;
             }
         });
 
-        var rotationMatrix = new Matrix();
-        var planeNormalTowardsCamera = new Vector3();
-        var localPlaneNormalTowardsCamera = new Vector3();
+        this.dragBehavior.onDragEndObservable.add(() => {
+            dragDistance = 0;
+            this.updateRotationCircle(this._rotationCircle, rotationCirclePaths, dragDistance, dragPlanePoint);
+            this._gizmoMesh.addChild(this._rotationCircle);    // Add rotation circle back to parent mesh after drag behavior
+            this._dragging = false;
+        });
 
         var tmpSnapEvent = { snapDistance: 0 };
         var currentSnapDragDistance = 0;
@@ -118,11 +155,13 @@ export class PlaneRotationGizmo extends Gizmo {
                     localPlaneNormalTowardsCamera = Vector3.TransformCoordinates(planeNormalTowardsCamera, rotationMatrix);
                 }
                 // Flip up vector depending on which side the camera is on
+                let cameraFlipped = false;
                 if (gizmoLayer.utilityLayerScene.activeCamera) {
                     var camVec = gizmoLayer.utilityLayerScene.activeCamera.position.subtract(nodeTranslation);
                     if (Vector3.Dot(camVec, localPlaneNormalTowardsCamera) > 0) {
                         planeNormalTowardsCamera.scaleInPlace(-1);
                         localPlaneNormalTowardsCamera.scaleInPlace(-1);
+                        cameraFlipped = true;
                     }
                 }
                 var halfCircleSide = Vector3.Dot(localPlaneNormalTowardsCamera, cross) > 0.0;
@@ -145,6 +184,9 @@ export class PlaneRotationGizmo extends Gizmo {
                     }
                 }
 
+                dragDistance += cameraFlipped ? -angle : angle;
+                this.updateRotationCircle(this._rotationCircle, rotationCirclePaths, dragDistance, dragPlanePoint);
+
                 // Convert angle and axis to quaternion (http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm)
                 var quaternionCoefficient = Math.sin(angle / 2);
                 amountToRotate.set(planeNormalTowardsCamera.x * quaternionCoefficient, planeNormalTowardsCamera.y * quaternionCoefficient, planeNormalTowardsCamera.z * quaternionCoefficient, Math.cos(angle / 2));
@@ -177,22 +219,50 @@ export class PlaneRotationGizmo extends Gizmo {
             }
         });
 
+        var light = gizmoLayer._getSharedGizmoLight();
+        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
+
+        const cache: GizmoAxisCache = {
+            colliderMeshes: [ collider ],
+            gizmoMeshes: [ rotationMesh ],
+            material: this._coloredMaterial,
+            hoverMaterial: this._hoverMaterial,
+            disableMaterial: this._disableMaterial,
+            active: false
+        };
+        this._parent?.addToAxisCache(this._gizmoMesh, cache);
+
         this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
             if (this._customMeshSet) {
                 return;
             }
-            this._isHovered = !!(pointerInfo.pickInfo && (this._rootMesh.getChildMeshes().indexOf(<Mesh>pointerInfo.pickInfo.pickedMesh) != -1));
-            var material = this._isHovered ? hoverMaterial : coloredMaterial;
-            this._rootMesh.getChildMeshes().forEach((m) => {
-                m.material = material;
-                if ((<LinesMesh>m).color) {
-                    (<LinesMesh>m).color = material.diffuseColor;
-                }
-            });
+            this._isHovered = !!(cache.colliderMeshes.indexOf(<Mesh>pointerInfo?.pickInfo?.pickedMesh) != -1);
+            if (!this._parent) {
+                var material = this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial;
+                cache.gizmoMeshes.forEach((m: Mesh) => {
+                    m.material = material;
+                    if ((<LinesMesh>m).color) {
+                        (<LinesMesh>m).color = material.diffuseColor;
+                    }
+                });
+            }
         });
+    }
 
-        var light = gizmoLayer._getSharedGizmoLight();
-        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
+    /** Create Geometry for Gizmo */
+    private _createGizmoMesh(parentMesh: AbstractMesh, thickness: number, tessellation: number) {
+        let collider = Mesh.CreateTorus("ignore", 0.6, 0.03 * thickness, tessellation, this.gizmoLayer.utilityLayerScene);
+        collider.visibility = 0;
+        let rotationMesh = Mesh.CreateTorus("", 0.6, 0.005 * thickness, tessellation, this.gizmoLayer.utilityLayerScene);
+        rotationMesh.material = this._coloredMaterial;
+
+        // Position arrow pointing in its drag axis
+        rotationMesh.rotation.x = Math.PI / 2;
+        collider.rotation.x = Math.PI / 2;
+
+        parentMesh.addChild(rotationMesh);
+        parentMesh.addChild(collider);
+        return { rotationMesh, collider };
     }
 
     protected _attachedNodeChanged(value: Nullable<Node>) {
@@ -201,6 +271,72 @@ export class PlaneRotationGizmo extends Gizmo {
         }
     }
 
+    private setupRotationCircle(paths: Vector3[][], parentMesh: AbstractMesh): Mesh {
+        const fillRadians = 0;
+        const step = PlaneRotationGizmo._CircleConstants.pi2 / PlaneRotationGizmo._CircleConstants.tessellation;
+        for (let p = -Math.PI / 2; p < Math.PI / 2 - 1.5; p += step / 2) {
+            const path: Vector3[] = [];
+            for (let i = 0; i < PlaneRotationGizmo._CircleConstants.pi2 * PlaneRotationGizmo._CircleConstants.rotationCircleRange + 0.01; i += step) {
+                if (i < fillRadians) {
+                    const x = PlaneRotationGizmo._CircleConstants.radius * Math.sin(i) * Math.cos(p);
+                    const z = PlaneRotationGizmo._CircleConstants.radius * Math.cos(i) * Math.cos(p);
+                    const y = 0;
+                    path.push(new Vector3(x, y, z));
+                } else {
+                    path.push(new Vector3(0, 0, 0));
+                }
+            }
+
+            paths.push(path);
+        }
+
+        const mat = new StandardMaterial("", this.gizmoLayer.utilityLayerScene);
+        mat.diffuseColor = Color3.Yellow();
+        mat.backFaceCulling = false;
+        const mesh = Mesh.CreateRibbon("rotationCircle", paths, false, false, 0, this.gizmoLayer.utilityLayerScene, true);
+        mesh.material = mat;
+        mesh.material.alpha = .25;
+        mesh.rotation.x = Math.PI / 2;
+        parentMesh.addChild(mesh);
+        return mesh;
+    }
+
+    private updateRotationPath(pathArr: Vector3[][], newFill: number): void {
+        // To update the Ribbon, you have to mutate the pathArray in-place
+        const step = PlaneRotationGizmo._CircleConstants.pi2 / PlaneRotationGizmo._CircleConstants.tessellation;
+        let tessellationCounter = 0;
+        for (let p = -Math.PI / 2; p < Math.PI / 2 - 1.5; p += step / 2) {
+            const path = pathArr[tessellationCounter];
+            if (path) {
+                let radianCounter = 0;
+                for (let i = 0; i < PlaneRotationGizmo._CircleConstants.pi2 * PlaneRotationGizmo._CircleConstants.rotationCircleRange + 0.01; i += step) {
+                    if (path[radianCounter]) {
+                        if (i < Math.abs(newFill)) {
+                            const absI = (newFill > 0) ? i : i * -1;
+                            const absP = (newFill > 0) ? p : p * -1;
+                            path[radianCounter].set(
+                                PlaneRotationGizmo._CircleConstants.radius * Math.sin(absI) * Math.cos(absP),
+                                0,
+                                PlaneRotationGizmo._CircleConstants.radius * Math.cos(absI) * Math.cos(absP)
+                            );
+                        } else {
+                            path[radianCounter].set(0, 0, 0);
+                        }
+                    }
+
+                    radianCounter++;
+                }
+            }
+
+            tessellationCounter ++;
+        }
+    }
+
+    private updateRotationCircle(mesh: Mesh, paths: any[], newFill: number, dragPlanePoint: Vector3): void {
+        this.updateRotationPath(paths, newFill);
+        Mesh.CreateRibbon("rotationCircle", paths, false, false, 0, this.gizmoLayer.utilityLayerScene, undefined, undefined, mesh);
+    }
+
     /**
          * If the gizmo is enabled
          */
@@ -225,6 +361,17 @@ export class PlaneRotationGizmo extends Gizmo {
         this.onSnapObservable.clear();
         this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
         this.dragBehavior.detach();
+        if (this._gizmoMesh) {
+            this._gizmoMesh.dispose();
+        }
+        if (this._rotationCircle) {
+            this._rotationCircle.dispose();
+        }
+        [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
+            if (matl) {
+                matl.dispose();
+            }
+        });
         super.dispose();
     }
 }

+ 28 - 3
src/Gizmos/positionGizmo.ts

@@ -1,15 +1,17 @@
 import { Logger } from "../Misc/logger";
-import { Observable } from "../Misc/observable";
+import { Observable, Observer } from "../Misc/observable";
 import { Nullable } from "../types";
 import { Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { AxisDragGizmo } from "./axisDragGizmo";
 import { PlaneDragGizmo } from "./planeDragGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+import { PointerInfo } from "../Events/pointerEvents";
+import { GizmoManager } from './gizmoManager';
 /**
  * Gizmo that enables dragging a mesh along 3 axis
  */
@@ -45,6 +47,10 @@ export class PositionGizmo extends Gizmo {
     private _meshAttached: Nullable<AbstractMesh> = null;
     private _nodeAttached: Nullable<Node> = null;
     private _snapDistance: number;
+    private _observables: Observer<PointerInfo>[] = [];
+
+    /** Node Caching for quick lookup */
+    private _gizmoAxisCache: Map<Mesh, GizmoAxisCache> = new Map();
 
     /** Fires an event when any of it's sub gizmos are dragged */
     public onDragStartObservable = new Observable();
@@ -104,7 +110,7 @@ export class PositionGizmo extends Gizmo {
      * @param gizmoLayer The utility layer the gizmo will be added to
       @param thickness display gizmo axis thickness
      */
-    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, thickness: number = 1) {
+    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, thickness: number = 1, gizmoManager?: GizmoManager) {
         super(gizmoLayer);
         this.xGizmo = new AxisDragGizmo(new Vector3(1, 0, 0), Color3.Red().scale(0.5), gizmoLayer, this, thickness);
         this.yGizmo = new AxisDragGizmo(new Vector3(0, 1, 0), Color3.Green().scale(0.5), gizmoLayer, this, thickness);
@@ -124,6 +130,13 @@ export class PositionGizmo extends Gizmo {
         });
 
         this.attachedMesh = null;
+
+        if (gizmoManager) {
+            gizmoManager.addToAxisCache(this._gizmoAxisCache);
+        } else {
+            // Only subscribe to pointer event if gizmoManager isnt
+            Gizmo.GizmoAxisPointerObserver(gizmoLayer, this._gizmoAxisCache);
+        }
     }
 
     /**
@@ -193,6 +206,15 @@ export class PositionGizmo extends Gizmo {
     }
 
     /**
+     * Builds Gizmo Axis Cache to enable features such as hover state preservation and graying out other axis during manipulation
+     * @param mesh Axis gizmo mesh
+     * @param cache Gizmo axis definition used for reactive gizmo UI
+     */
+    public addToAxisCache(mesh: Mesh, cache: GizmoAxisCache) {
+        this._gizmoAxisCache.set(mesh, cache);
+    }
+
+    /**
      * Disposes of the gizmo
      */
     public dispose() {
@@ -201,6 +223,9 @@ export class PositionGizmo extends Gizmo {
                 gizmo.dispose();
             }
         });
+        this._observables.forEach((obs) => {
+            this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(obs);
+        });
         this.onDragStartObservable.clear();
         this.onDragEndObservable.clear();
     }

+ 28 - 4
src/Gizmos/rotationGizmo.ts

@@ -1,16 +1,17 @@
 import { Logger } from "../Misc/logger";
-import { Observable } from "../Misc/observable";
+import { Observable, Observer } from "../Misc/observable";
 import { Nullable } from "../types";
 import { Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { PlaneRotationGizmo } from "./planeRotationGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { Node } from "../node";
+import { PointerInfo } from "../Events/pointerEvents";
 import { TransformNode } from "../Meshes/transformNode";
-
+import { GizmoManager } from './gizmoManager';
 /**
  * Gizmo that enables rotating a mesh along 3 axis
  */
@@ -35,6 +36,10 @@ export class RotationGizmo extends Gizmo {
 
     private _meshAttached: Nullable<AbstractMesh>;
     private _nodeAttached: Nullable<Node>;
+    private _observables: Observer<PointerInfo>[] = [];
+
+    /** Node Caching for quick lookup */
+    private _gizmoAxisCache: Map<Mesh, GizmoAxisCache> = new Map();
 
     public get attachedMesh() {
         return this._meshAttached;
@@ -94,7 +99,7 @@ export class RotationGizmo extends Gizmo {
      * @param useEulerRotation Use and update Euler angle instead of quaternion
      * @param thickness display gizmo axis thickness
      */
-    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, tessellation = 32, useEulerRotation = false, thickness: number = 1) {
+    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, tessellation = 32, useEulerRotation = false, thickness: number = 1, gizmoManager?: GizmoManager) {
         super(gizmoLayer);
         this.xGizmo = new PlaneRotationGizmo(new Vector3(1, 0, 0), Color3.Red().scale(0.5), gizmoLayer, tessellation, this, useEulerRotation, thickness);
         this.yGizmo = new PlaneRotationGizmo(new Vector3(0, 1, 0), Color3.Green().scale(0.5), gizmoLayer, tessellation, this, useEulerRotation, thickness);
@@ -112,6 +117,13 @@ export class RotationGizmo extends Gizmo {
 
         this.attachedMesh = null;
         this.attachedNode = null;
+
+        if (gizmoManager) {
+            gizmoManager.addToAxisCache(this._gizmoAxisCache);
+        } else {
+            // Only subscribe to pointer event if gizmoManager isnt
+            Gizmo.GizmoAxisPointerObserver(gizmoLayer, this._gizmoAxisCache);
+        }
     }
 
     public set updateGizmoRotationToMatchAttachedMesh(value: boolean) {
@@ -154,6 +166,15 @@ export class RotationGizmo extends Gizmo {
     }
 
     /**
+     * Builds Gizmo Axis Cache to enable features such as hover state preservation and graying out other axis during manipulation
+     * @param mesh Axis gizmo mesh
+     * @param cache Gizmo axis definition used for reactive gizmo UI
+     */
+    public addToAxisCache(mesh: Mesh, cache: GizmoAxisCache) {
+        this._gizmoAxisCache.set(mesh, cache);
+    }
+
+    /**
      * Disposes of the gizmo
      */
     public dispose() {
@@ -162,6 +183,9 @@ export class RotationGizmo extends Gizmo {
         this.zGizmo.dispose();
         this.onDragStartObservable.clear();
         this.onDragEndObservable.clear();
+        this._observables.forEach((obs) => {
+            this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(obs);
+        });
     }
 
     /**

+ 77 - 18
src/Gizmos/scaleGizmo.ts

@@ -1,15 +1,18 @@
 import { Logger } from "../Misc/logger";
-import { Observable } from "../Misc/observable";
+import { Observable, Observer } from "../Misc/observable";
 import { Nullable } from "../types";
 import { Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { PolyhedronBuilder } from "../Meshes/Builders/polyhedronBuilder";
-import { Gizmo } from "./gizmo";
+import { Gizmo, GizmoAxisCache } from "./gizmo";
 import { AxisScaleGizmo } from "./axisScaleGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { Mesh } from "../Meshes/mesh";
 import { Node } from "../node";
+import { PointerInfo } from "../Events/pointerEvents";
+import { StandardMaterial } from "../Materials/standardMaterial";
+import { GizmoManager } from './gizmoManager';
 /**
  * Gizmo that enables scaling a mesh along 3 axis
  */
@@ -38,6 +41,13 @@ export class ScaleGizmo extends Gizmo {
     private _uniformScalingMesh: Mesh;
     private _octahedron: Mesh;
     private _sensitivity: number = 1;
+    private _coloredMaterial: StandardMaterial;
+    private _hoverMaterial: StandardMaterial;
+    private _disableMaterial: StandardMaterial;
+    private _observables: Observer<PointerInfo>[] = [];
+
+    /** Node Caching for quick lookup */
+    private _gizmoAxisCache: Map<Mesh, GizmoAxisCache> = new Map();
 
     /** Fires an event when any of it's sub gizmos are dragged */
     public onDragStartObservable = new Observable();
@@ -92,26 +102,13 @@ export class ScaleGizmo extends Gizmo {
      * @param gizmoLayer The utility layer the gizmo will be added to
      * @param thickness display gizmo axis thickness
      */
-    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, thickness: number = 1) {
+    constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, thickness: number = 1, gizmoManager?: GizmoManager) {
         super(gizmoLayer);
+        this.uniformScaleGizmo = this._createUniformScaleMesh();
         this.xGizmo = new AxisScaleGizmo(new Vector3(1, 0, 0), Color3.Red().scale(0.5), gizmoLayer, this, thickness);
         this.yGizmo = new AxisScaleGizmo(new Vector3(0, 1, 0), Color3.Green().scale(0.5), gizmoLayer, this, thickness);
         this.zGizmo = new AxisScaleGizmo(new Vector3(0, 0, 1), Color3.Blue().scale(0.5), gizmoLayer, this, thickness);
 
-        // Create uniform scale gizmo
-        this.uniformScaleGizmo = new AxisScaleGizmo(new Vector3(0, 1, 0), Color3.Yellow().scale(0.5), gizmoLayer, this);
-        this.uniformScaleGizmo.updateGizmoRotationToMatchAttachedMesh = false;
-        this.uniformScaleGizmo.uniformScaling = true;
-        this._uniformScalingMesh = PolyhedronBuilder.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
-        this._uniformScalingMesh.scaling.scaleInPlace(0.02);
-        this._uniformScalingMesh.visibility = 0;
-        this._octahedron = PolyhedronBuilder.CreatePolyhedron("", { type: 1 }, this.uniformScaleGizmo.gizmoLayer.utilityLayerScene);
-        this._octahedron.scaling.scaleInPlace(0.007);
-        this._uniformScalingMesh.addChild(this._octahedron);
-        this.uniformScaleGizmo.setCustomMesh(this._uniformScalingMesh, true);
-        var light = gizmoLayer._getSharedGizmoLight();
-        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._octahedron);
-
         // Relay drag events
         [this.xGizmo, this.yGizmo, this.zGizmo, this.uniformScaleGizmo].forEach((gizmo) => {
             gizmo.dragBehavior.onDragStartObservable.add(() => {
@@ -124,6 +121,52 @@ export class ScaleGizmo extends Gizmo {
 
         this.attachedMesh = null;
         this.attachedNode = null;
+
+        if (gizmoManager) {
+            gizmoManager.addToAxisCache(this._gizmoAxisCache);
+        } else {
+            // Only subscribe to pointer event if gizmoManager isnt
+            Gizmo.GizmoAxisPointerObserver(gizmoLayer, this._gizmoAxisCache);
+        }
+    }
+
+    /** Create Geometry for Gizmo */
+    private _createUniformScaleMesh(): AxisScaleGizmo {
+        this._coloredMaterial = new StandardMaterial("", this.gizmoLayer.utilityLayerScene);
+        this._coloredMaterial.diffuseColor = Color3.Gray();
+
+        this._hoverMaterial = new StandardMaterial("", this.gizmoLayer.utilityLayerScene);
+        this._hoverMaterial.diffuseColor = Color3.Yellow();
+
+        this._disableMaterial = new StandardMaterial("", this.gizmoLayer.utilityLayerScene);
+        this._disableMaterial.diffuseColor = Color3.Gray();
+        this._disableMaterial.alpha = 0.4;
+
+        const uniformScaleGizmo = new AxisScaleGizmo(new Vector3(0, 1, 0), Color3.Gray().scale(0.5), this.gizmoLayer, this);
+        uniformScaleGizmo.updateGizmoRotationToMatchAttachedMesh = false;
+        uniformScaleGizmo.uniformScaling = true;
+        this._uniformScalingMesh = PolyhedronBuilder.CreatePolyhedron("uniform", { type: 1 }, uniformScaleGizmo.gizmoLayer.utilityLayerScene);
+        this._uniformScalingMesh.scaling.scaleInPlace(0.01);
+        this._uniformScalingMesh.visibility = 0;
+        this._octahedron = PolyhedronBuilder.CreatePolyhedron("", { type: 1 }, uniformScaleGizmo.gizmoLayer.utilityLayerScene);
+        this._octahedron.scaling.scaleInPlace(0.007);
+        this._uniformScalingMesh.addChild(this._octahedron);
+        uniformScaleGizmo.setCustomMesh(this._uniformScalingMesh, true);
+        var light = this.gizmoLayer._getSharedGizmoLight();
+        light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._octahedron);
+
+        const cache: GizmoAxisCache = {
+            gizmoMeshes: [this._octahedron, this._uniformScalingMesh],
+            colliderMeshes: [this._uniformScalingMesh],
+            material: this._coloredMaterial,
+            hoverMaterial: this._hoverMaterial,
+            disableMaterial: this._disableMaterial,
+            active: false
+        };
+
+        this.addToAxisCache(uniformScaleGizmo._rootMesh, cache);
+
+        return uniformScaleGizmo;
     }
 
     public set updateGizmoRotationToMatchAttachedMesh(value: boolean) {
@@ -189,6 +232,15 @@ export class ScaleGizmo extends Gizmo {
     }
 
     /**
+     * Builds Gizmo Axis Cache to enable features such as hover state preservation and graying out other axis during manipulation
+     * @param mesh Axis gizmo mesh
+     * @param cache Gizmo axis definition used for reactive gizmo UI
+     */
+    public addToAxisCache(mesh: Mesh, cache: GizmoAxisCache) {
+        this._gizmoAxisCache.set(mesh, cache);
+    }
+
+    /**
      * Disposes of the gizmo
      */
     public dispose() {
@@ -197,13 +249,20 @@ export class ScaleGizmo extends Gizmo {
                 gizmo.dispose();
             }
         });
+        this._observables.forEach((obs) => {
+            this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(obs);
+        });
         this.onDragStartObservable.clear();
         this.onDragEndObservable.clear();
-
         [this._uniformScalingMesh, this._octahedron].forEach((msh) => {
             if (msh) {
                 msh.dispose();
             }
         });
+        [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
+            if (matl) {
+                matl.dispose();
+            }
+        });
     }
 }

+ 1 - 1
src/Materials/Node/Blocks/Dual/reflectionTextureBaseBlock.ts

@@ -135,7 +135,7 @@ export abstract class ReflectionTextureBaseBlock extends NodeMaterialBlock {
             worldInput.output.connectTo(this.world);
         }
 
-        if (!this.view.isConnected) {
+        if (this.view && !this.view.isConnected) {
             let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
 
             if (!viewInput) {

+ 0 - 146
src/Materials/Node/Blocks/PBR/ambientOcclusionBlock.ts

@@ -1,146 +0,0 @@
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
-import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
-import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
-import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
-import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
-import { Nullable } from '../../../../types';
-import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
-import { _TypeStore } from '../../../../Misc/typeStore';
-import { AbstractMesh } from '../../../../Meshes/abstractMesh';
-import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
-import { Scene } from '../../../../scene';
-
-/**
- * Block used to implement the ambient occlusion module of the PBR material
- */
-export class AmbientOcclusionBlock extends NodeMaterialBlock {
-
-    /**
-     * Create a new AmbientOcclusionBlock
-     * @param name defines the block name
-     */
-    public constructor(name: string) {
-        super(name, NodeMaterialBlockTargets.Fragment);
-
-        this._isUnique = true;
-
-        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("directLightIntensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-
-        this.registerOutput("ambientOcc", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
-            new NodeMaterialConnectionPointCustomObject("ambientOcc", this, NodeMaterialConnectionPointDirection.Output, AmbientOcclusionBlock, "AOBlock", "ambientOcc"));
-    }
-
-    /**
-     * Specifies if the ambient texture contains the ambient occlusion information in its red channel only.
-     */
-    @editableInPropertyPage("Ambient in gray scale", PropertyTypeForEdition.Boolean, "AMBIENT", { "notifiers": { "update": true }})
-    public useAmbientInGrayScale: boolean = false;
-
-    /**
-     * Initialize the block and prepare the context for build
-     * @param state defines the state that will be used for the build
-     */
-    public initialize(state: NodeMaterialBuildState) {
-        state._excludeVariableName("aoOut");
-    }
-
-    /**
-     * Gets the current class name
-     * @returns the class name
-     */
-    public getClassName() {
-        return "AmbientOcclusionBlock";
-    }
-
-    /**
-     * Gets the texture input component
-     */
-    public get texture(): NodeMaterialConnectionPoint {
-        return this._inputs[0];
-    }
-
-    /**
-     * Gets the texture intensity component
-     */
-    public get intensity(): NodeMaterialConnectionPoint {
-        return this._inputs[1];
-    }
-
-    /**
-     * Gets the direct light intensity input component
-     */
-    public get directLightIntensity(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
-    }
-
-    /**
-     * Gets the ambient occlusion object output component
-     */
-    public get ambientOcc(): NodeMaterialConnectionPoint {
-        return this._outputs[0];
-    }
-
-    /**
-     * Gets the main code of the block (fragment side)
-     * @param block instance of an AmbientOcclusionBlock or null if the code must be generated without an active ambient occlusion module
-     * @returns the shader code
-     */
-    public static GetCode(block: Nullable<AmbientOcclusionBlock>): string {
-        let code = `ambientOcclusionOutParams aoOut;\r\n`;
-
-        const aoTexture = block?.texture.isConnected ? block.texture.associatedVariableName : "vec3(0.)";
-        const aoIntensity = block?.intensity.isConnected ? block.intensity.associatedVariableName : "1.";
-
-        code += `ambientOcclusionBlock(
-            #ifdef AMBIENT
-                ${aoTexture},
-                vec4(0., 1.0, ${aoIntensity}, 0.),
-            #endif
-                aoOut
-            );\r\n`;
-
-        return code;
-    }
-
-    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
-        super.prepareDefines(mesh, nodeMaterial, defines);
-
-        defines.setValue("AMBIENT", this.texture.isConnected, true);
-        defines.setValue("AMBIENTINGRAYSCALE", this.useAmbientInGrayScale, true);
-    }
-
-    protected _buildBlock(state: NodeMaterialBuildState) {
-        if (state.target === NodeMaterialBlockTargets.Fragment) {
-            state.sharedData.blocksWithDefines.push(this);
-        }
-
-        return this;
-    }
-
-    protected _dumpPropertiesCode() {
-        let codeString: string;
-
-        codeString = `${this._codeVariableName}.useAmbientInGrayScale = ${this.useAmbientInGrayScale};\r\n`;
-
-        return codeString;
-    }
-
-    public serialize(): any {
-        let serializationObject = super.serialize();
-
-        serializationObject.useAmbientInGrayScale = this.useAmbientInGrayScale;
-
-        return serializationObject;
-    }
-
-    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
-        super._deserialize(serializationObject, scene, rootUrl);
-
-        this.useAmbientInGrayScale = serializationObject.useAmbientInGrayScale;
-    }
-}
-
-_TypeStore.RegisteredTypes["BABYLON.AmbientOcclusionBlock"] = AmbientOcclusionBlock;

+ 4 - 13
src/Materials/Node/Blocks/PBR/anisotropyBlock.ts

@@ -35,7 +35,6 @@ export class AnisotropyBlock extends NodeMaterialBlock {
 
         this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("direction", NodeMaterialBlockConnectionPointTypes.Vector2, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, true); // need this property and the next one in case there's no PerturbNormal block connected to the main PBR block
         this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector4, true);
 
@@ -75,24 +74,17 @@ export class AnisotropyBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the texture input component
-     */
-    public get texture(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
-    }
-
-    /**
      * Gets the uv input component
      */
     public get uv(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
+        return this._inputs[2];
     }
 
     /**
      * Gets the worldTangent input component
      */
     public get worldTangent(): NodeMaterialConnectionPoint {
-        return this._inputs[4];
+        return this._inputs[3];
     }
 
     /**
@@ -160,13 +152,12 @@ export class AnisotropyBlock extends NodeMaterialBlock {
 
         const intensity = this.intensity.isConnected ? this.intensity.associatedVariableName : "1.0";
         const direction = this.direction.isConnected ? this.direction.associatedVariableName : "vec2(1., 0.)";
-        const texture = this.texture.isConnected ? this.texture.associatedVariableName : "vec3(0.)";
 
         code += `anisotropicOutParams anisotropicOut;
             anisotropicBlock(
                 vec3(${direction}, ${intensity}),
             #ifdef ANISOTROPIC_TEXTURE
-                ${texture},
+                vec3(0.),
             #endif
                 TBN,
                 normalW,
@@ -181,7 +172,7 @@ export class AnisotropyBlock extends NodeMaterialBlock {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
         defines.setValue("ANISOTROPIC", true);
-        defines.setValue("ANISOTROPIC_TEXTURE", this.texture.isConnected, true);
+        defines.setValue("ANISOTROPIC_TEXTURE", false, true);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

+ 48 - 26
src/Materials/Node/Blocks/PBR/clearCoatBlock.ts

@@ -16,6 +16,8 @@ import { SubMesh } from '../../../../Meshes/subMesh';
 import { Effect } from '../../../effect';
 import { PBRMetallicRoughnessBlock } from './pbrMetallicRoughnessBlock';
 import { PerturbNormalBlock } from '../Fragment/perturbNormalBlock';
+import { PBRClearCoatConfiguration } from '../../../PBR/pbrClearCoatConfiguration';
+import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
 
 /**
  * Block used to implement the clear coat module of the PBR material
@@ -35,9 +37,8 @@ export class ClearCoatBlock extends NodeMaterialBlock {
 
         this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
         this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("ior", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("bumpTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("indexOfRefraction", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("normalMapColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintAtDistance", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
@@ -49,6 +50,12 @@ export class ClearCoatBlock extends NodeMaterialBlock {
     }
 
     /**
+     * Defines if the F0 value should be remapped to account for the interface change in the material.
+     */
+    @editableInPropertyPage("Remap F0 on interface change", PropertyTypeForEdition.Boolean, "ADVANCED")
+    public remapF0OnInterfaceChange: boolean = true;
+
+    /**
      * Initialize the block and prepare the context for build
      * @param state defines the state that will be used for the build
      */
@@ -85,57 +92,50 @@ export class ClearCoatBlock extends NodeMaterialBlock {
     /**
      * Gets the ior input component
      */
-    public get ior(): NodeMaterialConnectionPoint {
+    public get indexOfRefraction(): NodeMaterialConnectionPoint {
         return this._inputs[2];
     }
 
     /**
-     * Gets the texture input component
-     */
-    public get texture(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
-    }
-
-    /**
      * Gets the bump texture input component
      */
-    public get bumpTexture(): NodeMaterialConnectionPoint {
-        return this._inputs[4];
+    public get normalMapColor(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
     }
 
     /**
      * Gets the uv input component
      */
     public get uv(): NodeMaterialConnectionPoint {
-        return this._inputs[5];
+        return this._inputs[4];
     }
 
     /**
      * Gets the tint color input component
      */
     public get tintColor(): NodeMaterialConnectionPoint {
-        return this._inputs[6];
+        return this._inputs[5];
     }
 
     /**
      * Gets the tint "at distance" input component
      */
     public get tintAtDistance(): NodeMaterialConnectionPoint {
-        return this._inputs[7];
+        return this._inputs[6];
     }
 
     /**
      * Gets the tint thickness input component
      */
     public get tintThickness(): NodeMaterialConnectionPoint {
-        return this._inputs[8];
+        return this._inputs[7];
     }
 
     /**
      * Gets the world tangent input component
      */
     public get worldTangent(): NodeMaterialConnectionPoint {
-        return this._inputs[9];
+        return this._inputs[8];
     }
 
     /**
@@ -157,18 +157,19 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
         defines.setValue("CLEARCOAT", true);
-        defines.setValue("CLEARCOAT_TEXTURE", this.texture.isConnected, true);
+        defines.setValue("CLEARCOAT_TEXTURE", false, true);
         defines.setValue("CLEARCOAT_USE_ROUGHNESS_FROM_MAINTEXTURE", true, true);
         defines.setValue("CLEARCOAT_TINT", this.tintColor.isConnected || this.tintThickness.isConnected || this.tintAtDistance.isConnected, true);
-        defines.setValue("CLEARCOAT_BUMP", this.bumpTexture.isConnected, true);
-        defines.setValue("CLEARCOAT_DEFAULTIOR", this.ior.isConnected ? this.ior.connectInputBlock!.value === 1.5 : false, true);
+        defines.setValue("CLEARCOAT_BUMP", this.normalMapColor.isConnected, true);
+        defines.setValue("CLEARCOAT_DEFAULTIOR", this.indexOfRefraction.isConnected ? this.indexOfRefraction.connectInputBlock!.value === PBRClearCoatConfiguration._DefaultIndexOfRefraction : true, true);
+        defines.setValue("CLEARCOAT_REMAP_F0", this.remapF0OnInterfaceChange, true);
     }
 
     public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
         super.bind(effect, nodeMaterial, mesh);
 
         // Clear Coat Refraction params
-        const indexOfRefraction = this.ior.connectInputBlock?.value ?? 1.5;
+        const indexOfRefraction = this.indexOfRefraction.connectInputBlock?.value ?? PBRClearCoatConfiguration._DefaultIndexOfRefraction;
 
         const a = 1 - indexOfRefraction;
         const b = 1 + indexOfRefraction;
@@ -230,8 +231,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
 
         const intensity = ccBlock?.intensity.isConnected ? ccBlock.intensity.associatedVariableName : "1.";
         const roughness = ccBlock?.roughness.isConnected ? ccBlock.roughness.associatedVariableName : "0.";
-        const texture = ccBlock?.texture.isConnected ? ccBlock.texture.associatedVariableName : "vec2(0.)";
-        const bumpTexture = ccBlock?.bumpTexture.isConnected ? ccBlock.bumpTexture.associatedVariableName : "vec4(0.)";
+        const normalMapColor = ccBlock?.normalMapColor.isConnected ? ccBlock.normalMapColor.associatedVariableName : "vec3(0.)";
         const uv = ccBlock?.uv.isConnected ? ccBlock.uv.associatedVariableName : "vec2(0.)";
 
         const tintColor = ccBlock?.tintColor.isConnected ? ccBlock.tintColor.associatedVariableName : "vec3(1.)";
@@ -262,7 +262,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
                 vClearCoatParams,
                 specularEnvironmentR0,
             #ifdef CLEARCOAT_TEXTURE
-                ${texture}.rg,
+                vec2(0.),
             #endif
             #ifdef CLEARCOAT_TINT
                 vClearCoatTintParams,
@@ -274,7 +274,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
             #endif
             #ifdef CLEARCOAT_BUMP
                 vec2(0., 1.),
-                ${bumpTexture},
+                vec4(${normalMapColor}, 0.),
                 ${uv},
                 #if defined(${vTBNAvailable ? "TANGENT" : "IGNORE"}) && defined(NORMAL)
                     vTBN,
@@ -332,6 +332,28 @@ export class ClearCoatBlock extends NodeMaterialBlock {
 
         return this;
     }
+
+    protected _dumpPropertiesCode() {
+        let codeString: string = "";
+
+        codeString += `${this._codeVariableName}.remapF0OnInterfaceChange = ${this.remapF0OnInterfaceChange};\r\n`;
+
+        return codeString;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.remapF0OnInterfaceChange = this.remapF0OnInterfaceChange;
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.remapF0OnInterfaceChange = serializationObject.remapF0OnInterfaceChange ?? true;
+    }
 }
 
 _TypeStore.RegisteredTypes["BABYLON.ClearCoatBlock"] = ClearCoatBlock;

+ 0 - 2
src/Materials/Node/Blocks/PBR/index.ts

@@ -1,7 +1,5 @@
 export * from "./pbrMetallicRoughnessBlock";
 export * from "./sheenBlock";
-export * from "./ambientOcclusionBlock";
-export * from "./reflectivityBlock";
 export * from "./anisotropyBlock";
 export * from "./reflectionBlock";
 export * from "./clearCoatBlock";

+ 201 - 117
src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts

@@ -17,9 +17,7 @@ import { PBRBaseMaterial } from '../../../PBR/pbrBaseMaterial';
 import { Scene } from '../../../../scene';
 import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
 import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
-import { AmbientOcclusionBlock } from './ambientOcclusionBlock';
 import { SheenBlock } from './sheenBlock';
-import { ReflectivityBlock } from './reflectivityBlock';
 import { BaseTexture } from '../../../Textures/baseTexture';
 import { BRDFTextureTools } from '../../../../Misc/brdfTextureTools';
 import { MaterialFlags } from '../../../materialFlags';
@@ -30,6 +28,7 @@ import { SubSurfaceBlock } from './subSurfaceBlock';
 import { RefractionBlock } from './refractionBlock';
 import { PerturbNormalBlock } from '../Fragment/perturbNormalBlock';
 import { Constants } from '../../../../Engines/constants';
+import { Color3, TmpColors } from '../../../../Maths/math.color';
 
 const mapOutputToVariable: { [name: string] : [string, string] } = {
     "ambient":      ["finalAmbient", ""],
@@ -62,6 +61,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     private _environmentBrdfSamplerName: string;
     private _vNormalWName: string;
     private _invertNormalName: string;
+    private _metallicReflectanceColor: Color3 = Color3.White();
+    private _metallicF0Factor = 1;
+    private _vMetallicReflectanceFactorsName: string;
 
     /**
      * Create a new ReflectionBlock
@@ -74,36 +76,36 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
 
         this.registerInput("worldPosition", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Vertex);
         this.registerInput("worldNormal", NodeMaterialBlockConnectionPointTypes.Vector4, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("perturbedNormal", NodeMaterialBlockConnectionPointTypes.Vector4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false);
         this.registerInput("cameraPosition", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("baseColor", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("opacityTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("perturbedNormal", NodeMaterialBlockConnectionPointTypes.Vector4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("baseColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("metallic", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("ambientOcc", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("opacity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("indexOfRefraction", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("ambientColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("reflectivity", NodeMaterialBlockConnectionPointTypes.Object, false, NodeMaterialBlockTargets.Fragment,
-            new NodeMaterialConnectionPointCustomObject("reflectivity", this, NodeMaterialConnectionPointDirection.Input, ReflectivityBlock, "ReflectivityBlock"));
-        this.registerInput("ambientOcc", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
-            new NodeMaterialConnectionPointCustomObject("ambientOcc", this, NodeMaterialConnectionPointDirection.Input, AmbientOcclusionBlock, "AOBlock"));
         this.registerInput("reflection", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("reflection", this, NodeMaterialConnectionPointDirection.Input, ReflectionBlock, "ReflectionBlock"));
-        this.registerInput("sheen", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
-            new NodeMaterialConnectionPointCustomObject("sheen", this, NodeMaterialConnectionPointDirection.Input, SheenBlock, "SheenBlock"));
         this.registerInput("clearcoat", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("clearcoat", this, NodeMaterialConnectionPointDirection.Input, ClearCoatBlock, "ClearCoatBlock"));
+        this.registerInput("sheen", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
+            new NodeMaterialConnectionPointCustomObject("sheen", this, NodeMaterialConnectionPointDirection.Input, SheenBlock, "SheenBlock"));
         this.registerInput("subsurface", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("subsurface", this, NodeMaterialConnectionPointDirection.Input, SubSurfaceBlock, "SubSurfaceBlock"));
         this.registerInput("anisotropy", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("anisotropy", this, NodeMaterialConnectionPointDirection.Input, AnisotropyBlock, "AnisotropyBlock"));
-        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, true);
 
-        this.registerOutput("ambient", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
-        this.registerOutput("diffuse", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
-        this.registerOutput("specular", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
-        this.registerOutput("sheenDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("ambientClr", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("diffuseDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("specularDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("clearcoatDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("sheenDir", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("diffuseInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("specularInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
-        this.registerOutput("sheenInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("clearcoatInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("sheenInd", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("refraction", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("lighting", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Fragment);
         this.registerOutput("shadow", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
@@ -143,37 +145,24 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     public lightFalloff = 0;
 
     /**
-     * Specifies that the alpha is coming form the albedo channel alpha channel for alpha blending.
-     */
-    @editableInPropertyPage("Alpha from albedo", PropertyTypeForEdition.Boolean, "TRANSPARENCY", { "notifiers": { "update": true }})
-    public useAlphaFromAlbedoTexture: boolean = false;
-
-    /**
      * Specifies that alpha test should be used
      */
-    @editableInPropertyPage("Alpha Testing", PropertyTypeForEdition.Boolean, "TRANSPARENCY")
+    @editableInPropertyPage("Alpha Testing", PropertyTypeForEdition.Boolean, "OPACITY")
     public useAlphaTest: boolean = false;
 
     /**
      * Defines the alpha limits in alpha test mode.
      */
-    @editableInPropertyPage("Alpha CutOff", PropertyTypeForEdition.Float, "TRANSPARENCY", { min: 0, max: 1, "notifiers": { "update": true }})
+    @editableInPropertyPage("Alpha CutOff", PropertyTypeForEdition.Float, "OPACITY", { min: 0, max: 1, "notifiers": { "update": true }})
     public alphaTestCutoff: number = 0.5;
 
     /**
      * Specifies that alpha blending should be used
      */
-    @editableInPropertyPage("Alpha blending", PropertyTypeForEdition.Boolean, "TRANSPARENCY")
+    @editableInPropertyPage("Alpha blending", PropertyTypeForEdition.Boolean, "OPACITY")
     public useAlphaBlending: boolean = false;
 
     /**
-     * Defines if the alpha value should be determined via the rgb values.
-     * If true the luminance of the pixel might be used to find the corresponding alpha value.
-     */
-    @editableInPropertyPage("Get alpha from opacity texture RGB", PropertyTypeForEdition.Boolean, "TRANSPARENCY", { "notifiers": { "update": true }})
-    public opacityRGB: boolean = false;
-
-    /**
      * Specifies that the material will keeps the reflection highlights over a transparent surface (only the most luminous ones).
      * A car glass is a good exemple of that. When the street lights reflects on it you can not see what is behind.
      */
@@ -264,18 +253,8 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         { label: "Anisotropic Tangents", value: 12 },
         { label: "Anisotropic Bitangents", value: 13 },
         // Maps
-        { label: "Albedo Map", value: 20 },
-        { label: "Ambient Map", value: 21 },
-        { label: "Opacity Map", value: 22 },
         //{ label: "Emissive Map", value: 23 },
         //{ label: "Light Map", value: 24 },
-        { label: "Metallic Map", value: 25 },
-        { label: "Reflectivity Map", value: 26 },
-        { label: "ClearCoat Map", value: 27 },
-        { label: "ClearCoat Tint Map", value: 28 },
-        { label: "Sheen Map", value: 29 },
-        { label: "Anisotropic Map", value: 30 },
-        { label: "Thickness Map", value: 31 },
         // Env
         { label: "Env Refraction", value: 40 },
         { label: "Env Reflection", value: 41 },
@@ -342,6 +321,13 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         state._excludeVariableName("surfaceAlbedo");
         state._excludeVariableName("alpha");
 
+        state._excludeVariableName("aoOut");
+
+        state._excludeVariableName("baseColor");
+        state._excludeVariableName("reflectivityOut");
+        state._excludeVariableName("microSurface");
+        state._excludeVariableName("roughness");
+
         state._excludeVariableName("NdotVUnclamped");
         state._excludeVariableName("NdotV");
         state._excludeVariableName("alphaG");
@@ -394,9 +380,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the perturbed normal input component
+     * Gets the view matrix parameter
      */
-    public get perturbedNormal(): NodeMaterialConnectionPoint {
+    public get view(): NodeMaterialConnectionPoint {
         return this._inputs[2];
     }
 
@@ -408,114 +394,128 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the base color input component
+     * Gets the perturbed normal input component
      */
-    public get baseColor(): NodeMaterialConnectionPoint {
+    public get perturbedNormal(): NodeMaterialConnectionPoint {
         return this._inputs[4];
     }
 
     /**
-     * Gets the opacity texture input component
+     * Gets the base color input component
      */
-    public get opacityTexture(): NodeMaterialConnectionPoint {
+    public get baseColor(): NodeMaterialConnectionPoint {
         return this._inputs[5];
     }
 
     /**
-     * Gets the ambient color input component
+     * Gets the metallic input component
      */
-    public get ambientColor(): NodeMaterialConnectionPoint {
+    public get metallic(): NodeMaterialConnectionPoint {
         return this._inputs[6];
     }
 
     /**
-     * Gets the reflectivity object parameters
+     * Gets the roughness input component
      */
-    public get reflectivity(): NodeMaterialConnectionPoint {
+    public get roughness(): NodeMaterialConnectionPoint {
         return this._inputs[7];
     }
 
     /**
-     * Gets the ambient occlusion object parameters
+     * Gets the ambient occlusion input component
      */
     public get ambientOcc(): NodeMaterialConnectionPoint {
         return this._inputs[8];
     }
 
     /**
-     * Gets the reflection object parameters
+     * Gets the opacity input component
      */
-    public get reflection(): NodeMaterialConnectionPoint {
+    public get opacity(): NodeMaterialConnectionPoint {
         return this._inputs[9];
     }
 
     /**
-     * Gets the sheen object parameters
+     * Gets the index of refraction input component
      */
-    public get sheen(): NodeMaterialConnectionPoint {
+    public get indexOfRefraction(): NodeMaterialConnectionPoint {
         return this._inputs[10];
     }
 
     /**
-     * Gets the clear coat object parameters
+     * Gets the ambient color input component
      */
-    public get clearcoat(): NodeMaterialConnectionPoint {
+    public get ambientColor(): NodeMaterialConnectionPoint {
         return this._inputs[11];
     }
 
     /**
-     * Gets the sub surface object parameters
+     * Gets the reflection object parameters
      */
-    public get subsurface(): NodeMaterialConnectionPoint {
+    public get reflection(): NodeMaterialConnectionPoint {
         return this._inputs[12];
     }
 
     /**
-     * Gets the anisotropy object parameters
+     * Gets the clear coat object parameters
      */
-    public get anisotropy(): NodeMaterialConnectionPoint {
+    public get clearcoat(): NodeMaterialConnectionPoint {
         return this._inputs[13];
     }
 
     /**
-     * Gets the view matrix parameter
+     * Gets the sheen object parameters
      */
-    public get view(): NodeMaterialConnectionPoint {
+    public get sheen(): NodeMaterialConnectionPoint {
         return this._inputs[14];
     }
 
     /**
+     * Gets the sub surface object parameters
+     */
+    public get subsurface(): NodeMaterialConnectionPoint {
+        return this._inputs[15];
+    }
+
+    /**
+     * Gets the anisotropy object parameters
+     */
+    public get anisotropy(): NodeMaterialConnectionPoint {
+        return this._inputs[16];
+    }
+
+    /**
      * Gets the ambient output component
      */
-    public get ambient(): NodeMaterialConnectionPoint {
+    public get ambientClr(): NodeMaterialConnectionPoint {
         return this._outputs[0];
     }
 
     /**
      * Gets the diffuse output component
      */
-    public get diffuse(): NodeMaterialConnectionPoint {
+    public get diffuseDir(): NodeMaterialConnectionPoint {
         return this._outputs[1];
     }
 
     /**
      * Gets the specular output component
      */
-    public get specular(): NodeMaterialConnectionPoint {
+    public get specularDir(): NodeMaterialConnectionPoint {
         return this._outputs[2];
     }
 
     /**
-     * Gets the sheen output component
+     * Gets the clear coat output component
      */
-    public get sheenDir(): NodeMaterialConnectionPoint {
+    public get clearcoatDir(): NodeMaterialConnectionPoint {
         return this._outputs[3];
     }
 
     /**
-     * Gets the clear coat output component
+     * Gets the sheen output component
      */
-    public get clearcoatDir(): NodeMaterialConnectionPoint {
+    public get sheenDir(): NodeMaterialConnectionPoint {
         return this._outputs[4];
     }
 
@@ -534,16 +534,16 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the indirect sheen output component
+     * Gets the indirect clear coat output component
      */
-    public get sheenIndirect(): NodeMaterialConnectionPoint {
+    public get clearcoatIndirect(): NodeMaterialConnectionPoint {
         return this._outputs[7];
     }
 
     /**
-     * Gets the indirect clear coat output component
+     * Gets the indirect sheen output component
      */
-    public get clearcoatIndirect(): NodeMaterialConnectionPoint {
+    public get sheenIndirect(): NodeMaterialConnectionPoint {
         return this._outputs[8];
     }
 
@@ -585,6 +585,16 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             }
             cameraPositionInput.output.connectTo(this.cameraPosition);
         }
+
+        if (!this.view.isConnected) {
+            let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
+
+            if (!viewInput) {
+                viewInput = new InputBlock("view");
+                viewInput.setAsSystemValue(NodeMaterialSystemValues.View);
+            }
+            viewInput.output.connectTo(this.view);
+        }
     }
 
     public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
@@ -597,7 +607,19 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         defines.setValue("LODBASEDMICROSFURACE", this._scene.getEngine().getCaps().textureLOD);
 
         // Albedo & Opacity
-        defines.setValue("OPACITY", this.opacityTexture.isConnected, true);
+        defines.setValue("ALBEDO", false, true);
+        defines.setValue("OPACITY", this.opacity.isConnected, true);
+
+        // Ambient occlusion
+        defines.setValue("AMBIENT", true, true);
+        defines.setValue("AMBIENTINGRAYSCALE", false, true);
+
+        // Reflectivity
+        defines.setValue("REFLECTIVITY", false, true);
+        defines.setValue("AOSTOREINMETALMAPRED", false, true);
+        defines.setValue("METALLNESSSTOREINMETALMAPBLUE", false, true);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", false, true);
+        defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN",  false, true);
 
         // Lighting & colors
         if (this.lightFalloff === PBRBaseMaterial.LIGHTFALLOFF_STANDARD) {
@@ -612,11 +634,13 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
 
         // Transparency
+        const alphaTestCutOffString = this.alphaTestCutoff.toString();
+
         defines.setValue("ALPHABLEND", this.useAlphaBlending, true);
-        defines.setValue("ALPHAFROMALBEDO", this.useAlphaFromAlbedoTexture, true);
+        defines.setValue("ALPHAFROMALBEDO", false, true);
         defines.setValue("ALPHATEST", this.useAlphaTest, true);
-        defines.setValue("ALPHATESTVALUE", this.alphaTestCutoff, true);
-        defines.setValue("OPACITYRGB", this.opacityRGB, true);
+        defines.setValue("ALPHATESTVALUE", alphaTestCutOffString.indexOf('.') < 0 ? alphaTestCutOffString + "." : alphaTestCutOffString, true);
+        defines.setValue("OPACITYRGB", false, true);
 
         // Rendering
         defines.setValue("RADIANCEOVERALPHA", this.useRadianceOverAlpha, true);
@@ -713,6 +737,21 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         effect.setFloat(this._invertNormalName, invertNormal ? -1 : 1);
 
         effect.setFloat4("vLightingIntensity", this.directIntensity, 1, this.environmentIntensity * this._scene.environmentIntensity, this.specularIntensity);
+
+        // reflectivity bindings
+        const outside_ior = 1; // consider air as clear coat and other layers would remap in the shader.
+        const ior = this.indexOfRefraction.connectInputBlock?.value ?? 1.5;
+
+        // We are here deriving our default reflectance from a common value for none metallic surface.
+        // Based of the schlick fresnel approximation model
+        // for dielectrics.
+        const f0 = Math.pow((ior - outside_ior) / (ior + outside_ior), 2);
+
+        // Tweak the default F0 and F90 based on our given setup
+        this._metallicReflectanceColor.scaleToRef(f0 * this._metallicF0Factor, TmpColors.Color3[0]);
+        const metallicF90 = this._metallicF0Factor;
+
+        effect.setColor4(this._vMetallicReflectanceFactorsName, TmpColors.Color3[0], metallicF90);
     }
 
     private _injectVertexCode(state: NodeMaterialBuildState) {
@@ -744,6 +783,10 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
 
         const reflectionBlock = this.reflection.isConnected ? this.reflection.connectedPoint?.ownerBlock as ReflectionBlock : null;
 
+        if (reflectionBlock) {
+            reflectionBlock.viewConnectionPoint = this.view;
+        }
+
         state.compilationString += reflectionBlock?.handleVertexSide(state) ?? "";
 
         state._emitUniformFromString("vDebugMode", "vec2", "defined(IGNORE) || DEBUGMODE > 0");
@@ -773,24 +816,20 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
     }
 
-    /**
-     * Gets the code corresponding to the albedo/opacity module
-     * @returns the shader code
-     */
-    public getAlbedoOpacityCode(): string {
+    private _getAlbedoOpacityCode(): string {
         let code = `albedoOpacityOutParams albedoOpacityOut;\r\n`;
 
-        const albedoColor = this.baseColor.isConnected ? this.baseColor.associatedVariableName : "vec4(1., 1., 1., 1.)";
-        const opacityTexture = this.opacityTexture.isConnected ? this.opacityTexture.associatedVariableName : "";
+        const albedoColor = this.baseColor.isConnected ? this.baseColor.associatedVariableName : "vec3(1.)";
+        const opacity = this.opacity.isConnected ? this.opacity.associatedVariableName : "1.";
 
         code += `albedoOpacityBlock(
-                ${albedoColor},
+                vec4(${albedoColor}, 1.),
             #ifdef ALBEDO
                 vec4(1.),
                 vec2(1., 1.),
             #endif
             #ifdef OPACITY
-                ${opacityTexture},
+                vec4(${opacity}),
                 vec2(1., 1.),
             #endif
                 albedoOpacityOut
@@ -802,6 +841,64 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         return code;
     }
 
+    private _getAmbientOcclusionCode(): string {
+        let code = `ambientOcclusionOutParams aoOut;\r\n`;
+
+        const ao = this.ambientOcc.isConnected ? this.ambientOcc.associatedVariableName : "1.";
+
+        code += `ambientOcclusionBlock(
+            #ifdef AMBIENT
+                vec3(${ao}),
+                vec4(0., 1.0, 1.0, 0.),
+            #endif
+                aoOut
+            );\r\n`;
+
+        return code;
+    }
+
+    private _getReflectivityCode(state: NodeMaterialBuildState): string {
+        let code = `reflectivityOutParams reflectivityOut;\r\n`;
+
+        const aoIntensity = "1.";
+
+        this._vMetallicReflectanceFactorsName = state._getFreeVariableName("vMetallicReflectanceFactors");
+        state._emitUniformFromString(this._vMetallicReflectanceFactorsName, "vec4");
+
+        code += `vec3 baseColor = surfaceAlbedo;
+
+            reflectivityBlock(
+                vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.),
+            #ifdef METALLICWORKFLOW
+                surfaceAlbedo,
+                ${this._vMetallicReflectanceFactorsName},
+            #endif
+            #ifdef REFLECTIVITY
+                vec3(0., 0., ${aoIntensity}),
+                vec4(1.),
+            #endif
+            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY)  && defined(AOSTOREINMETALMAPRED)
+                aoOut.ambientOcclusionColor,
+            #endif
+            #ifdef MICROSURFACEMAP
+                microSurfaceTexel, <== not handled!
+            #endif
+                reflectivityOut
+            );
+
+            float microSurface = reflectivityOut.microSurface;
+            float roughness = reflectivityOut.roughness;
+
+            #ifdef METALLICWORKFLOW
+                surfaceAlbedo = reflectivityOut.surfaceAlbedo;
+            #endif
+            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY) && defined(AOSTOREINMETALMAPRED)
+                aoOut.ambientOcclusionColor = reflectivityOut.ambientOcclusionColor;
+            #endif\r\n`;
+
+        return code;
+    }
+
     protected _buildBlock(state: NodeMaterialBuildState) {
         super._buildBlock(state);
 
@@ -923,36 +1020,22 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         });
 
         // _____________________________ Albedo & Opacity ______________________________
-        state.compilationString += this.getAlbedoOpacityCode();
+        state.compilationString += this._getAlbedoOpacityCode();
 
         state.compilationString += state._emitCodeFromInclude("depthPrePass", comments);
 
         // _____________________________ AO  _______________________________
-        const aoBlock = this.ambientOcc.connectedPoint?.ownerBlock as Nullable<AmbientOcclusionBlock>;
-
-        state.compilationString += AmbientOcclusionBlock.GetCode(aoBlock);
+        state.compilationString += this._getAmbientOcclusionCode();
 
         state.compilationString += state._emitCodeFromInclude("pbrBlockLightmapInit", comments);
 
         // _____________________________ UNLIT  _______________________________
-
         state.compilationString += `#ifdef UNLIT
                 vec3 diffuseBase = vec3(1., 1., 1.);
             #else\r\n`;
 
         // _____________________________ Reflectivity _______________________________
-        const subsurfaceBlock = this.subsurface.isConnected ? this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock : null;
-        const refractionBlock = this.subsurface.isConnected ? (this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock).refraction.connectedPoint?.ownerBlock as RefractionBlock : null;
-
-        const reflectivityBlock = this.reflectivity.connectedPoint?.ownerBlock as Nullable<ReflectivityBlock> ?? null;
-
-        if (reflectivityBlock) {
-            reflectivityBlock.indexOfRefractionConnectionPoint = refractionBlock?.indexOfRefraction ?? null;
-        }
-
-        const aoIntensity = aoBlock?.intensity.isConnected ? aoBlock.intensity.associatedVariableName : "1.";
-
-        state.compilationString += reflectivityBlock?.getCode(state, aoIntensity) ?? "";
+        state.compilationString += this._getReflectivityCode(state);
 
         // _____________________________ Geometry info _________________________________
         state.compilationString += state._emitCodeFromInclude("pbrBlockGeometryInfo", comments, {
@@ -993,7 +1076,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         // ___________________ Compute Reflectance aka R0 F0 info _________________________
         state.compilationString += state._emitCodeFromInclude("pbrBlockReflectance0", comments, {
             replaceStrings: [
-                { search: /metallicReflectanceFactors/g, replace: reflectivityBlock?._vMetallicReflectanceFactorsName ?? "metallicReflectanceFactors" },
+                { search: /metallicReflectanceFactors/g, replace: this._vMetallicReflectanceFactorsName },
             ]
         });
         // ________________________________ Sheen ______________________________
@@ -1047,6 +1130,14 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         });
 
         // ___________________________________ SubSurface ______________________________________
+        const subsurfaceBlock = this.subsurface.isConnected ? this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock : null;
+        const refractionBlock = this.subsurface.isConnected ? (this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock).refraction.connectedPoint?.ownerBlock as RefractionBlock : null;
+
+        if (refractionBlock) {
+            refractionBlock.viewConnectionPoint = this.view;
+            refractionBlock.indexOfRefractionConnectionPoint = this.indexOfRefraction;
+        }
+
         state.compilationString += SubSurfaceBlock.GetCode(state, subsurfaceBlock, reflectionBlock, worldPosVarName);
 
         state._emitFunctionFromInclude("pbrBlockSubSurface", comments, {
@@ -1085,9 +1176,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         // _____________________________ Compute Final Unlit Components ________________________
         const aoColor = this.ambientColor.isConnected ? this.ambientColor.associatedVariableName : "vec3(0., 0., 0.)";
 
-        let aoDirectLightIntensity = aoBlock?.directLightIntensity.isConnected ? aoBlock.directLightIntensity.associatedVariableName : PBRBaseMaterial.DEFAULT_AO_ON_ANALYTICAL_LIGHTS.toString();
+        let aoDirectLightIntensity = PBRBaseMaterial.DEFAULT_AO_ON_ANALYTICAL_LIGHTS.toString();
 
-        if (!aoBlock?.directLightIntensity.isConnected && aoDirectLightIntensity.indexOf('.') === -1) {
+        if (aoDirectLightIntensity.indexOf('.') === -1) {
             aoDirectLightIntensity += ".";
         }
 
@@ -1119,7 +1210,6 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
                 { search: /vNormalW/g, replace: this._vNormalWName },
                 { search: /vPositionW/g, replace: worldPosVarName },
                 { search: /albedoTexture\.rgb;/g, replace: "vec3(1.);\r\ngl_FragColor.rgb = toGammaSpace(gl_FragColor.rgb);\r\n" },
-                { search: /opacityMap/g, replace: this.opacityTexture.associatedVariableName },
             ]
         });
 
@@ -1151,11 +1241,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         let codeString: string = "";
 
         codeString += `${this._codeVariableName}.lightFalloff = ${this.lightFalloff};\r\n`;
-        codeString += `${this._codeVariableName}.useAlphaFromAlbedoTexture = ${this.useAlphaFromAlbedoTexture};\r\n`;
         codeString += `${this._codeVariableName}.useAlphaTest = ${this.useAlphaTest};\r\n`;
         codeString += `${this._codeVariableName}.alphaTestCutoff = ${this.alphaTestCutoff};\r\n`;
         codeString += `${this._codeVariableName}.useAlphaBlending = ${this.useAlphaBlending};\r\n`;
-        codeString += `${this._codeVariableName}.opacityRGB = ${this.opacityRGB};\r\n`;
         codeString += `${this._codeVariableName}.useRadianceOverAlpha = ${this.useRadianceOverAlpha};\r\n`;
         codeString += `${this._codeVariableName}.useSpecularOverAlpha = ${this.useSpecularOverAlpha};\r\n`;
         codeString += `${this._codeVariableName}.enableSpecularAntiAliasing = ${this.enableSpecularAntiAliasing};\r\n`;
@@ -1181,11 +1269,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
 
         serializationObject.lightFalloff = this.lightFalloff;
-        serializationObject.useAlphaFromAlbedoTexture = this.useAlphaFromAlbedoTexture;
         serializationObject.useAlphaTest = this.useAlphaTest;
         serializationObject.alphaTestCutoff = this.alphaTestCutoff;
         serializationObject.useAlphaBlending = this.useAlphaBlending;
-        serializationObject.opacityRGB = this.opacityRGB;
         serializationObject.useRadianceOverAlpha = this.useRadianceOverAlpha;
         serializationObject.useSpecularOverAlpha = this.useSpecularOverAlpha;
         serializationObject.enableSpecularAntiAliasing = this.enableSpecularAntiAliasing;
@@ -1211,11 +1297,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
 
         this.lightFalloff = serializationObject.lightFalloff ?? 0;
-        this.useAlphaFromAlbedoTexture = serializationObject.useAlphaFromAlbedoTexture;
         this.useAlphaTest = serializationObject.useAlphaTest;
         this.alphaTestCutoff = serializationObject.alphaTestCutoff;
         this.useAlphaBlending = serializationObject.useAlphaBlending;
-        this.opacityRGB = serializationObject.opacityRGB;
         this.useRadianceOverAlpha = serializationObject.useRadianceOverAlpha;
         this.useSpecularOverAlpha = serializationObject.useSpecularOverAlpha;
         this.enableSpecularAntiAliasing = serializationObject.enableSpecularAntiAliasing;

+ 12 - 4
src/Materials/Node/Blocks/PBR/reflectionBlock.ts

@@ -36,7 +36,7 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
     private _scene: Scene;
 
     /**
-     * The three properties below are set by the main PBR block prior to calling methods of this class.
+     * The properties below are set by the main PBR block prior to calling methods of this class.
      * This is to avoid having to add them as inputs here whereas they are already inputs of the main block, so already known.
      * It's less burden on the user side in the editor part.
     */
@@ -47,6 +47,8 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
     public worldNormalConnectionPoint: NodeMaterialConnectionPoint;
     /** @hidden */
     public cameraPositionConnectionPoint: NodeMaterialConnectionPoint;
+    /** @hidden */
+    public viewConnectionPoint: NodeMaterialConnectionPoint;
 
     /**
      * Defines if the material uses spherical harmonics vs spherical polynomials for the
@@ -72,7 +74,6 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
 
         this.registerInput("position", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Vertex);
         this.registerInput("world", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Vertex);
-        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Fragment);
         this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
 
         this.registerOutput("reflection", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
@@ -126,14 +127,14 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
      * Gets the view input component
      */
     public get view(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
+        return this.viewConnectionPoint;
     }
 
     /**
      * Gets the color input component
      */
     public get color(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
+        return this._inputs[2];
     }
 
     /**
@@ -423,6 +424,9 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
     protected _dumpPropertiesCode() {
         let codeString: string = super._dumpPropertiesCode();
 
+        if (this.texture) {
+            codeString += `${this._codeVariableName}.texture.gammaSpace = ${this.texture.gammaSpace});\r\n`;
+        }
         codeString += `${this._codeVariableName}.useSphericalHarmonics = ${this.useSphericalHarmonics};\r\n`;
         codeString += `${this._codeVariableName}.forceIrradianceInFragment = ${this.forceIrradianceInFragment};\r\n`;
 
@@ -434,6 +438,7 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
 
         serializationObject.useSphericalHarmonics = this.useSphericalHarmonics;
         serializationObject.forceIrradianceInFragment = this.forceIrradianceInFragment;
+        serializationObject.gammaSpace = this.texture?.gammaSpace ?? true;
 
         return serializationObject;
     }
@@ -443,6 +448,9 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
 
         this.useSphericalHarmonics = serializationObject.useSphericalHarmonics;
         this.forceIrradianceInFragment = serializationObject.forceIrradianceInFragment;
+        if (this.texture) {
+            this.texture.gammaSpace = serializationObject.gammaSpace;
+        }
     }
 }
 

+ 0 - 243
src/Materials/Node/Blocks/PBR/reflectivityBlock.ts

@@ -1,243 +0,0 @@
-import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
-import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
-import { NodeMaterialBlock } from '../../nodeMaterialBlock';
-import { _TypeStore } from '../../../../Misc/typeStore';
-import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
-import { AbstractMesh } from '../../../../Meshes/abstractMesh';
-import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
-import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
-import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
-import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
-import { Scene } from '../../../../scene';
-import { Nullable } from '../../../../types';
-import { Color3 } from '../../../../Maths/math.color';
-import { TmpColors } from '../../../../Maths/math.color';
-import { Mesh } from '../../../../Meshes/mesh';
-import { SubMesh } from '../../../../Meshes/subMesh';
-import { Effect } from '../../../effect';
-
-/**
- * Block used to implement the reflectivity module of the PBR material
- */
-export class ReflectivityBlock extends NodeMaterialBlock {
-
-    private _metallicReflectanceColor: Color3 = Color3.White();
-    private _metallicF0Factor = 1;
-
-    /** @hidden */
-    public _vMetallicReflectanceFactorsName: string;
-
-    /**
-     * The property below is set by the main PBR block prior to calling methods of this class.
-    */
-
-    /** @hidden */
-    public indexOfRefractionConnectionPoint: Nullable<NodeMaterialConnectionPoint>;
-
-    /**
-     * Specifies if the metallic texture contains the ambient occlusion information in its red channel.
-     */
-    @editableInPropertyPage("AO from red channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
-    public useAmbientOcclusionFromMetallicTextureRed: boolean = false;
-
-    /**
-     * Specifies if the metallic texture contains the metallness information in its blue channel.
-     */
-    @editableInPropertyPage("Metallness from blue channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
-    public useMetallnessFromMetallicTextureBlue: boolean = true;
-
-    /**
-     * Specifies if the metallic texture contains the roughness information in its alpha channel.
-     */
-    @editableInPropertyPage("Roughness from alpha channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
-    public useRoughnessFromMetallicTextureAlpha: boolean = false;
-
-    /**
-     * Specifies if the metallic texture contains the roughness information in its green channel.
-     */
-    @editableInPropertyPage("Roughness from green channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
-    public useRoughnessFromMetallicTextureGreen: boolean = true;
-
-    /**
-     * Create a new ReflectivityBlock
-     * @param name defines the block name
-     */
-    public constructor(name: string) {
-        super(name, NodeMaterialBlockTargets.Fragment);
-
-        this._isUnique = true;
-
-        this.registerInput("metallic", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
-
-        this.registerOutput("reflectivity", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
-            new NodeMaterialConnectionPointCustomObject("reflectivity", this, NodeMaterialConnectionPointDirection.Output, ReflectivityBlock, "ReflectivityBlock"));
-    }
-
-    /**
-     * Initialize the block and prepare the context for build
-     * @param state defines the state that will be used for the build
-     */
-    public initialize(state: NodeMaterialBuildState) {
-        state._excludeVariableName("baseColor");
-        state._excludeVariableName("reflectivityOut");
-        state._excludeVariableName("microSurface");
-        state._excludeVariableName("roughness");
-    }
-
-    /**
-     * Gets the current class name
-     * @returns the class name
-     */
-    public getClassName() {
-        return "ReflectivityBlock";
-    }
-
-    /**
-     * Gets the metallic input component
-     */
-    public get metallic(): NodeMaterialConnectionPoint {
-        return this._inputs[0];
-    }
-
-    /**
-     * Gets the roughness input component
-     */
-    public get roughness(): NodeMaterialConnectionPoint {
-        return this._inputs[1];
-    }
-
-    /**
-     * Gets the texture input component
-     */
-    public get texture(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
-    }
-
-    /**
-     * Gets the reflectivity object output component
-     */
-    public get reflectivity(): NodeMaterialConnectionPoint {
-        return this._outputs[0];
-    }
-
-    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
-        super.bind(effect, nodeMaterial, mesh);
-
-        const outside_ior = 1; // consider air as clear coat and other layers would remap in the shader.
-        const ior = this.indexOfRefractionConnectionPoint?.connectInputBlock?.value ?? 1.5;
-
-        // We are here deriving our default reflectance from a common value for none metallic surface.
-        // Based of the schlick fresnel approximation model
-        // for dielectrics.
-        const f0 = Math.pow((ior - outside_ior) / (ior + outside_ior), 2);
-
-        // Tweak the default F0 and F90 based on our given setup
-        this._metallicReflectanceColor.scaleToRef(f0 * this._metallicF0Factor, TmpColors.Color3[0]);
-        const metallicF90 = this._metallicF0Factor;
-
-        effect.setColor4(this._vMetallicReflectanceFactorsName, TmpColors.Color3[0], metallicF90);
-    }
-
-    /**
-     * Gets the main code of the block (fragment side)
-     * @param state current state of the node material building
-     * @param aoIntensityVarName name of the variable with the ambient occlusion intensity
-     * @returns the shader code
-     */
-    public getCode(state: NodeMaterialBuildState, aoIntensityVarName: string): string {
-        const metalRoughTexture = this.texture.isConnected ? this.texture.connectedPoint?.associatedVariableName : null;
-
-        this._vMetallicReflectanceFactorsName = state._getFreeVariableName("vMetallicReflectanceFactors");
-
-        state._emitUniformFromString(this._vMetallicReflectanceFactorsName, "vec4");
-
-        // note: metallic F0 factor = 1
-        let code = `vec3 baseColor = surfaceAlbedo;
-            reflectivityOutParams reflectivityOut;
-
-            reflectivityBlock(
-                vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.),
-            #ifdef METALLICWORKFLOW
-                surfaceAlbedo,
-                ${this._vMetallicReflectanceFactorsName},
-            #endif
-            #ifdef REFLECTIVITY
-                vec3(0., 0., ${aoIntensityVarName}),
-                ${metalRoughTexture},
-            #endif
-            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY)  && defined(AOSTOREINMETALMAPRED)
-                aoOut.ambientOcclusionColor,
-            #endif
-            #ifdef MICROSURFACEMAP
-                microSurfaceTexel, <== not handled!
-            #endif
-                reflectivityOut
-            );
-
-            float microSurface = reflectivityOut.microSurface;
-            float roughness = reflectivityOut.roughness;
-
-            #ifdef METALLICWORKFLOW
-                surfaceAlbedo = reflectivityOut.surfaceAlbedo;
-            #endif
-            #if defined(METALLICWORKFLOW) && defined(REFLECTIVITY) && defined(AOSTOREINMETALMAPRED)
-                aoOut.ambientOcclusionColor = reflectivityOut.ambientOcclusionColor;
-            #endif\r\n`;
-
-        return code;
-    }
-
-    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
-        super.prepareDefines(mesh, nodeMaterial, defines);
-
-        defines.setValue("REFLECTIVITY", this.texture.isConnected, true);
-        defines.setValue("AOSTOREINMETALMAPRED", this.useAmbientOcclusionFromMetallicTextureRed, true);
-        defines.setValue("METALLNESSSTOREINMETALMAPBLUE", this.useMetallnessFromMetallicTextureBlue, true);
-        defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", this.useRoughnessFromMetallicTextureAlpha, true);
-        defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN",  !this.useRoughnessFromMetallicTextureAlpha && this.useRoughnessFromMetallicTextureGreen, true);
-    }
-
-    protected _buildBlock(state: NodeMaterialBuildState) {
-        if (state.target === NodeMaterialBlockTargets.Fragment) {
-            state.sharedData.blocksWithDefines.push(this);
-            state.sharedData.bindableBlocks.push(this);
-        }
-
-        return this;
-    }
-
-    protected _dumpPropertiesCode() {
-        let codeString: string = "";
-
-        codeString += `${this._codeVariableName}.useAmbientOcclusionFromMetallicTextureRed = ${this.useAmbientOcclusionFromMetallicTextureRed};\r\n`;
-        codeString += `${this._codeVariableName}.useMetallnessFromMetallicTextureBlue = ${this.useMetallnessFromMetallicTextureBlue};\r\n`;
-        codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureAlpha = ${this.useRoughnessFromMetallicTextureAlpha};\r\n`;
-        codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureGreen = ${this.useRoughnessFromMetallicTextureGreen};\r\n`;
-
-        return codeString;
-    }
-
-    public serialize(): any {
-        let serializationObject = super.serialize();
-
-        serializationObject.useAmbientOcclusionFromMetallicTextureRed = this.useAmbientOcclusionFromMetallicTextureRed;
-        serializationObject.useMetallnessFromMetallicTextureBlue = this.useMetallnessFromMetallicTextureBlue;
-        serializationObject.useRoughnessFromMetallicTextureAlpha = this.useRoughnessFromMetallicTextureAlpha;
-        serializationObject.useRoughnessFromMetallicTextureGreen = this.useRoughnessFromMetallicTextureGreen;
-
-        return serializationObject;
-    }
-
-    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
-        super._deserialize(serializationObject, scene, rootUrl);
-
-        this.useAmbientOcclusionFromMetallicTextureRed = serializationObject.useAmbientOcclusionFromMetallicTextureRed;
-        this.useMetallnessFromMetallicTextureBlue = serializationObject.useMetallnessFromMetallicTextureBlue;
-        this.useRoughnessFromMetallicTextureAlpha = serializationObject.useRoughnessFromMetallicTextureAlpha;
-        this.useRoughnessFromMetallicTextureGreen = serializationObject.useRoughnessFromMetallicTextureGreen;
-    }
-}
-
-_TypeStore.RegisteredTypes["BABYLON.ReflectivityBlock"] = ReflectivityBlock;

+ 16 - 13
src/Materials/Node/Blocks/PBR/refractionBlock.ts

@@ -46,6 +46,18 @@ export class RefractionBlock extends NodeMaterialBlock {
     private _scene: Scene;
 
     /**
+     * The properties below are set by the main PBR block prior to calling methods of this class.
+     * This is to avoid having to add them as inputs here whereas they are already inputs of the main block, so already known.
+     * It's less burden on the user side in the editor part.
+    */
+
+    /** @hidden */
+    public viewConnectionPoint: NodeMaterialConnectionPoint;
+
+    /** @hidden */
+    public indexOfRefractionConnectionPoint: NodeMaterialConnectionPoint;
+
+    /**
      * This parameters will make the material used its opacity to control how much it is refracting aginst not.
      * Materials half opaque for instance using refraction could benefit from this control.
      */
@@ -73,9 +85,7 @@ export class RefractionBlock extends NodeMaterialBlock {
         this._isUnique = true;
 
         this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("indexOfRefraction", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintAtDistance", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("view", NodeMaterialBlockConnectionPointTypes.Matrix, false, NodeMaterialBlockTargets.Fragment);
 
         this.registerOutput("refraction", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("refraction", this, NodeMaterialConnectionPointDirection.Output, RefractionBlock, "RefractionBlock"));
@@ -97,24 +107,17 @@ export class RefractionBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the index of refraction input component
-     */
-    public get indexOfRefraction(): NodeMaterialConnectionPoint {
-        return this._inputs[1];
-    }
-
-    /**
      * Gets the tint at distance input component
      */
     public get tintAtDistance(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
+        return this._inputs[1];
     }
 
     /**
      * Gets the view input component
      */
     public get view(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
+        return this.viewConnectionPoint;
     }
 
     /**
@@ -146,7 +149,7 @@ export class RefractionBlock extends NodeMaterialBlock {
             intensityInput.output.connectTo(this.intensity);
         }
 
-        if (!this.view.isConnected) {
+        if (this.view && !this.view.isConnected) {
             let viewInput = material.getInputBlockByPredicate((b) => b.systemValue === NodeMaterialSystemValues.View);
 
             if (!viewInput) {
@@ -213,7 +216,7 @@ export class RefractionBlock extends NodeMaterialBlock {
             }
         }
 
-        const indexOfRefraction = this.indexOfRefraction.connectInputBlock?.value ?? 1.5;
+        const indexOfRefraction = this.indexOfRefractionConnectionPoint.connectInputBlock?.value ?? 1.5;
 
         effect.setFloat4(this._vRefractionInfosName, refractionTexture.level, 1 / indexOfRefraction, depth, this.invertRefractionY ? -1 : 1);
 

+ 20 - 48
src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts

@@ -4,7 +4,6 @@ import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
 import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
 import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
 import { _TypeStore } from '../../../../Misc/typeStore';
-import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
 import { InputBlock } from '../Input/inputBlock';
 import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
 import { NodeMaterial, NodeMaterialDefines } from '../../nodeMaterial';
@@ -27,12 +26,10 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
 
         this._isUnique = true;
 
-        this.registerInput("minThickness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("maxThickness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("thicknessTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("thickness", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("translucencyIntensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("translucencyDiffusionDistance", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("translucencyDiffusionDist", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("refraction", NodeMaterialBlockConnectionPointTypes.Object, true, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("refraction", this, NodeMaterialConnectionPointDirection.Input, RefractionBlock, "RefractionBlock"));
 
@@ -41,15 +38,6 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Stores the intensity of the different subsurface effects in the thickness texture.
-     * * the green channel is the translucency intensity.
-     * * the blue channel is the scattering intensity.
-     * * the alpha channel is the refraction intensity.
-     */
-    @editableInPropertyPage("Mask from thickness texture", PropertyTypeForEdition.Boolean, "PROPERTIES", { "notifiers": { "update": true }})
-    public useMaskFromThicknessTexture: boolean = false;
-
-    /**
      * Initialize the block and prepare the context for build
      * @param state defines the state that will be used for the build
      */
@@ -69,52 +57,38 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the min thickness input component
+     * Gets the thickness component
      */
-    public get minThickness(): NodeMaterialConnectionPoint {
+    public get thickness(): NodeMaterialConnectionPoint {
         return this._inputs[0];
     }
 
     /**
-     * Gets the max thickness input component
-     */
-    public get maxThickness(): NodeMaterialConnectionPoint {
-        return this._inputs[1];
-    }
-
-    /**
-     * Gets the thickness texture component
-     */
-    public get thicknessTexture(): NodeMaterialConnectionPoint {
-        return this._inputs[2];
-    }
-
-    /**
      * Gets the tint color input component
      */
     public get tintColor(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
+        return this._inputs[1];
     }
 
     /**
      * Gets the translucency intensity input component
      */
     public get translucencyIntensity(): NodeMaterialConnectionPoint {
-        return this._inputs[4];
+        return this._inputs[2];
     }
 
     /**
      * Gets the translucency diffusion distance input component
      */
-    public get translucencyDiffusionDistance(): NodeMaterialConnectionPoint {
-        return this._inputs[5];
+    public get translucencyDiffusionDist(): NodeMaterialConnectionPoint {
+        return this._inputs[3];
     }
 
     /**
      * Gets the refraction object parameters
      */
     public get refraction(): NodeMaterialConnectionPoint {
-        return this._inputs[6];
+        return this._inputs[4];
     }
 
     /**
@@ -125,22 +99,22 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
     }
 
     public autoConfigure(material: NodeMaterial) {
-        if (!this.minThickness.isConnected) {
-            let minThicknessInput = new InputBlock("SubSurface min thickness", NodeMaterialBlockTargets.Fragment, NodeMaterialBlockConnectionPointTypes.Float);
-            minThicknessInput.value = 0;
-            minThicknessInput.output.connectTo(this.minThickness);
+        if (!this.thickness.isConnected) {
+            let thicknessInput = new InputBlock("SubSurface thickness", NodeMaterialBlockTargets.Fragment, NodeMaterialBlockConnectionPointTypes.Float);
+            thicknessInput.value = 0;
+            thicknessInput.output.connectTo(this.thickness);
         }
     }
 
     public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
         super.prepareDefines(mesh, nodeMaterial, defines);
 
-        const translucencyEnabled = this.translucencyDiffusionDistance.isConnected || this.translucencyIntensity.isConnected;
+        const translucencyEnabled = this.translucencyDiffusionDist.isConnected || this.translucencyIntensity.isConnected;
 
         defines.setValue("SUBSURFACE", translucencyEnabled || this.refraction.isConnected, true);
         defines.setValue("SS_TRANSLUCENCY", translucencyEnabled, true);
-        defines.setValue("SS_THICKNESSANDMASK_TEXTURE", this.thicknessTexture.isConnected, true);
-        defines.setValue("SS_MASK_FROM_THICKNESS_TEXTURE", this.useMaskFromThicknessTexture, true);
+        defines.setValue("SS_THICKNESSANDMASK_TEXTURE", false, true);
+        defines.setValue("SS_MASK_FROM_THICKNESS_TEXTURE", false, true);
     }
 
     /**
@@ -154,12 +128,10 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
     public static GetCode(state: NodeMaterialBuildState, ssBlock: Nullable<SubSurfaceBlock>, reflectionBlock: Nullable<ReflectionBlock>, worldPosVarName: string): string {
         let code = "";
 
-        const minThickness = ssBlock?.minThickness.isConnected ? ssBlock.minThickness.associatedVariableName : "0.";
-        const maxThickness = ssBlock?.maxThickness.isConnected ? ssBlock.maxThickness.associatedVariableName : "1.";
-        const thicknessTexture = ssBlock?.thicknessTexture.isConnected ? ssBlock.thicknessTexture.associatedVariableName : "vec4(0.)";
+        const thickness = ssBlock?.thickness.isConnected ? ssBlock.thickness.associatedVariableName : "0.";
         const tintColor = ssBlock?.tintColor.isConnected ? ssBlock.tintColor.associatedVariableName : "vec3(1.)";
         const translucencyIntensity = ssBlock?.translucencyIntensity.isConnected ? ssBlock?.translucencyIntensity.associatedVariableName : "1.";
-        const translucencyDiffusionDistance = ssBlock?.translucencyDiffusionDistance.isConnected ? ssBlock?.translucencyDiffusionDistance.associatedVariableName : "vec3(1.)";
+        const translucencyDiffusionDistance = ssBlock?.translucencyDiffusionDist.isConnected ? ssBlock?.translucencyDiffusionDist.associatedVariableName : "vec3(1.)";
 
         const refractionBlock: Nullable<RefractionBlock> = (ssBlock?.refraction.isConnected ? ssBlock?.refraction.connectedPoint?.ownerBlock : null) as Nullable<RefractionBlock>;
 
@@ -172,7 +144,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
         code += `subSurfaceOutParams subSurfaceOut;
 
         #ifdef SUBSURFACE
-            vec2 vThicknessParam = vec2(${minThickness}, ${maxThickness} - ${minThickness});
+            vec2 vThicknessParam = vec2(0., ${thickness});
             vec4 vTintColor = vec4(${tintColor}, ${refractionTintAtDistance});
             vec3 vSubSurfaceIntensity = vec3(${refractionIntensity}, ${translucencyIntensity}, 0.);
 
@@ -183,7 +155,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock {
                 normalW,
                 specularEnvironmentReflectance,
             #ifdef SS_THICKNESSANDMASK_TEXTURE
-                ${thicknessTexture},
+                vec4(0.),
             #endif
             #ifdef REFLECTION
                 #ifdef SS_TRANSLUCENCY

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

@@ -591,7 +591,7 @@ export class NodeMaterialBlock {
 
     protected _dumpPropertiesCode() {
         let variableName = this._codeVariableName;
-        return `${variableName}.visibleInInspector = ${this.visibleInInspector}`;
+        return `${variableName}.visibleInInspector = ${this.visibleInInspector};\r\n`;
     }
 
     /** @hidden */

+ 2 - 1
src/Materials/PBR/pbrClearCoatConfiguration.ts

@@ -43,8 +43,9 @@ export class PBRClearCoatConfiguration {
     /**
      * This defaults to 1.5 corresponding to a 0.04 f0 or a 4% reflectance at normal incidence
      * The default fits with a polyurethane material.
+     * @hidden
      */
-    private static readonly _DefaultIndexOfRefraction = 1.5;
+    public static readonly _DefaultIndexOfRefraction = 1.5;
 
     private _isEnabled = false;
     /**

+ 22 - 154
src/Materials/Textures/baseTexture.ts

@@ -8,10 +8,10 @@ import { InternalTexture } from "../../Materials/Textures/internalTexture";
 import { Constants } from "../../Engines/constants";
 import { IAnimatable } from '../../Animations/animatable.interface';
 import { GUID } from '../../Misc/guid';
-import { ISize, Size } from '../../Maths/math.size';
 
 import "../../Misc/fileTools";
 import { ThinEngine } from '../../Engines/thinEngine';
+import { ThinTexture } from './thinTexture';
 
 declare type Animation = import("../../Animations/animation").Animation;
 
@@ -20,7 +20,7 @@ declare type Animation = import("../../Animations/animation").Animation;
  * It groups all the common properties the materials, post process, lights... might need
  * in order to make a correct use of the texture.
  */
-export class BaseTexture implements IAnimatable {
+export class BaseTexture extends ThinTexture implements IAnimatable {
     /**
      * Default anisotropic filtering level for the application.
      * It is set to 4 as a good tradeoff between perf and quality.
@@ -121,36 +121,32 @@ export class BaseTexture implements IAnimatable {
         return this._coordinatesMode;
     }
 
-    private _wrapU = Constants.TEXTURE_WRAP_ADDRESSMODE;
     /**
-    * | Value | Type               | Description |
-    * | ----- | ------------------ | ----------- |
-    * | 0     | CLAMP_ADDRESSMODE  |             |
-    * | 1     | WRAP_ADDRESSMODE   |             |
-    * | 2     | MIRROR_ADDRESSMODE |             |
-    */
+     * | Value | Type               | Description |
+     * | ----- | ------------------ | ----------- |
+     * | 0     | CLAMP_ADDRESSMODE  |             |
+     * | 1     | WRAP_ADDRESSMODE   |             |
+     * | 2     | MIRROR_ADDRESSMODE |             |
+     */
     @serialize()
     public get wrapU() {
         return this._wrapU;
     }
-
     public set wrapU(value: number) {
         this._wrapU = value;
     }
 
-    private _wrapV = Constants.TEXTURE_WRAP_ADDRESSMODE;
     /**
-    * | Value | Type               | Description |
-    * | ----- | ------------------ | ----------- |
-    * | 0     | CLAMP_ADDRESSMODE  |             |
-    * | 1     | WRAP_ADDRESSMODE   |             |
-    * | 2     | MIRROR_ADDRESSMODE |             |
-    */
+     * | Value | Type               | Description |
+     * | ----- | ------------------ | ----------- |
+     * | 0     | CLAMP_ADDRESSMODE  |             |
+     * | 1     | WRAP_ADDRESSMODE   |             |
+     * | 2     | MIRROR_ADDRESSMODE |             |
+     */
     @serialize()
     public get wrapV() {
         return this._wrapV;
     }
-
     public set wrapV(value: number) {
         this._wrapV = value;
     }
@@ -173,13 +169,14 @@ export class BaseTexture implements IAnimatable {
     @serialize()
     public anisotropicFilteringLevel = BaseTexture.DEFAULT_ANISOTROPIC_FILTERING_LEVEL;
 
+    private _isCube = false;
     /**
      * Define if the texture is a cube texture or if false a 2d texture.
      */
     @serialize()
     public get isCube(): boolean {
         if (!this._texture) {
-            return false;
+            return this._isCube;
         }
 
         return this._texture.isCube;
@@ -187,10 +184,10 @@ export class BaseTexture implements IAnimatable {
 
     public set isCube(value: boolean) {
         if (!this._texture) {
-            return;
+            this._isCube = value;
+        } else {
+            this._texture.isCube = value;
         }
-
-        this._texture.isCube = value;
     }
 
     /**
@@ -411,13 +408,7 @@ export class BaseTexture implements IAnimatable {
         this._onDisposeObserver = this.onDisposeObservable.add(callback);
     }
 
-    /**
-     * Define the current state of the loading sequence when in delayed load mode.
-     */
-    public delayLoadState = Constants.DELAYLOADSTATE_NONE;
-
     protected _scene: Nullable<Scene> = null;
-    protected _engine: Nullable<ThinEngine> = null;
 
     /** @hidden */
     public _texture: Nullable<InternalTexture> = null;
@@ -439,6 +430,8 @@ export class BaseTexture implements IAnimatable {
      * @param sceneOrEngine Define the scene or engine the texture blongs to
      */
     constructor(sceneOrEngine: Nullable<Scene | ThinEngine>) {
+        super(null);
+
         if (sceneOrEngine) {
             if (BaseTexture._isScene(sceneOrEngine)) {
                 this._scene = sceneOrEngine;
@@ -499,14 +492,6 @@ export class BaseTexture implements IAnimatable {
     }
 
     /**
-     * Get the underlying lower level texture from Babylon.
-     * @returns the insternal texture
-     */
-    public getInternalTexture(): Nullable<InternalTexture> {
-        return this._texture;
-    }
-
-    /**
      * Get if the texture is ready to be consumed (either it is ready or it is not blocking)
      * @returns true if ready or not blocking
      */
@@ -515,100 +500,6 @@ export class BaseTexture implements IAnimatable {
     }
 
     /**
-     * Get if the texture is ready to be used (downloaded, converted, mip mapped...).
-     * @returns true if fully ready
-     */
-    public isReady(): boolean {
-        if (this.delayLoadState === Constants.DELAYLOADSTATE_NOTLOADED) {
-            this.delayLoad();
-            return false;
-        }
-
-        if (this._texture) {
-            return this._texture.isReady;
-        }
-
-        return false;
-    }
-
-    private _cachedSize: ISize = Size.Zero();
-    /**
-     * Get the size of the texture.
-     * @returns the texture size.
-     */
-    public getSize(): ISize {
-        if (this._texture) {
-            if (this._texture.width) {
-                this._cachedSize.width = this._texture.width;
-                this._cachedSize.height = this._texture.height;
-                return this._cachedSize;
-            }
-
-            if (this._texture._size) {
-                this._cachedSize.width = this._texture._size;
-                this._cachedSize.height = this._texture._size;
-                return this._cachedSize;
-            }
-        }
-
-        return this._cachedSize;
-    }
-
-    /**
-     * Get the base size of the texture.
-     * It can be different from the size if the texture has been resized for POT for instance
-     * @returns the base size
-     */
-    public getBaseSize(): ISize {
-        if (!this.isReady() || !this._texture) {
-            return Size.Zero();
-        }
-
-        if (this._texture._size) {
-            return new Size(this._texture._size, this._texture._size);
-        }
-
-        return new Size(this._texture.baseWidth, this._texture.baseHeight);
-    }
-
-    /**
-     * Update the sampling mode of the texture.
-     * Default is Trilinear mode.
-     *
-     * | Value | Type               | Description |
-     * | ----- | ------------------ | ----------- |
-     * | 1     | NEAREST_SAMPLINGMODE or NEAREST_NEAREST_MIPLINEAR  | Nearest is: mag = nearest, min = nearest, mip = linear |
-     * | 2     | BILINEAR_SAMPLINGMODE or LINEAR_LINEAR_MIPNEAREST | Bilinear is: mag = linear, min = linear, mip = nearest |
-     * | 3     | TRILINEAR_SAMPLINGMODE or LINEAR_LINEAR_MIPLINEAR | Trilinear is: mag = linear, min = linear, mip = linear |
-     * | 4     | NEAREST_NEAREST_MIPNEAREST |             |
-     * | 5    | NEAREST_LINEAR_MIPNEAREST |             |
-     * | 6    | NEAREST_LINEAR_MIPLINEAR |             |
-     * | 7    | NEAREST_LINEAR |             |
-     * | 8    | NEAREST_NEAREST |             |
-     * | 9   | LINEAR_NEAREST_MIPNEAREST |             |
-     * | 10   | LINEAR_NEAREST_MIPLINEAR |             |
-     * | 11   | LINEAR_LINEAR |             |
-     * | 12   | LINEAR_NEAREST |             |
-     *
-     *    > _mag_: magnification filter (close to the viewer)
-     *    > _min_: minification filter (far from the viewer)
-     *    > _mip_: filter used between mip map levels
-     *@param samplingMode Define the new sampling mode of the texture
-     */
-    public updateSamplingMode(samplingMode: number): void {
-        if (!this._texture) {
-            return;
-        }
-
-        const engine = this._getEngine();
-        if (!engine) {
-            return;
-        }
-
-        engine.updateTextureSamplingMode(samplingMode, this._texture);
-    }
-
-    /**
      * Scales the texture if is `canRescale()`
      * @param ratio the resize factor we want to use to rescale
      */
@@ -652,12 +543,6 @@ export class BaseTexture implements IAnimatable {
     }
 
     /**
-     * Triggers the load sequence in delayed load mode.
-     */
-    public delayLoad(): void {
-    }
-
-    /**
      * Clones the texture.
      * @returns the cloned texture
      */
@@ -742,16 +627,6 @@ export class BaseTexture implements IAnimatable {
         }
     }
 
-    /**
-     * Release and destroy the underlying lower level texture aka internalTexture.
-     */
-    public releaseInternalTexture(): void {
-        if (this._texture) {
-            this._texture.dispose();
-            this._texture = null;
-        }
-    }
-
     /** @hidden */
     public get _lodTextureHigh(): Nullable<BaseTexture> {
         if (this._texture) {
@@ -797,18 +672,11 @@ export class BaseTexture implements IAnimatable {
             this._scene = null;
         }
 
-        if (this._texture === undefined) {
-            return;
-        }
-
-        // Release
-        this.releaseInternalTexture();
-
         // Callback
         this.onDisposeObservable.notifyObservers(this);
         this.onDisposeObservable.clear();
 
-        this._engine = null;
+        super.dispose();
     }
 
     /**

+ 1 - 1
src/Materials/Textures/cubeTexture.ts

@@ -305,7 +305,7 @@ export class CubeTexture extends BaseTexture {
         if (!this._texture) {
             const scene = this.getScene();
             if (this._prefiltered) {
-                this._texture = this._getEngine()!.createPrefilteredCubeTexture(this.url, scene, 0.8, 0, this._delayedOnLoad, undefined, this._format, undefined, this._createPolynomials);
+                this._texture = this._getEngine()!.createPrefilteredCubeTexture(this.url, scene, 0.8, 0, this._delayedOnLoad, undefined, this._format, forcedExtension, this._createPolynomials);
             }
             else {
                 this._texture = this._getEngine()!.createCubeTexture(this.url, scene, this._files, this._noMipmap, this._delayedOnLoad, null, this._format, forcedExtension, false, 0, 0, null, this._loaderOptions);

+ 286 - 0
src/Materials/Textures/thinTexture.ts

@@ -0,0 +1,286 @@
+import { Nullable } from "../../types";
+import { InternalTexture } from "../../Materials/Textures/internalTexture";
+import { Constants } from "../../Engines/constants";
+
+import { ISize, Size } from '../../Maths/math.size';
+
+import { ThinEngine } from '../../Engines/thinEngine';
+
+/**
+ * Base class of all the textures in babylon.
+ * It groups all the common properties required to work with Thin Engine.
+ */
+export class ThinTexture {
+    protected _wrapU = Constants.TEXTURE_WRAP_ADDRESSMODE;
+    /**
+    * | Value | Type               | Description |
+    * | ----- | ------------------ | ----------- |
+    * | 0     | CLAMP_ADDRESSMODE  |             |
+    * | 1     | WRAP_ADDRESSMODE   |             |
+    * | 2     | MIRROR_ADDRESSMODE |             |
+    */
+    public get wrapU() {
+        return this._wrapU;
+    }
+
+    public set wrapU(value: number) {
+        this._wrapU = value;
+    }
+
+    protected _wrapV = Constants.TEXTURE_WRAP_ADDRESSMODE;
+    /**
+    * | Value | Type               | Description |
+    * | ----- | ------------------ | ----------- |
+    * | 0     | CLAMP_ADDRESSMODE  |             |
+    * | 1     | WRAP_ADDRESSMODE   |             |
+    * | 2     | MIRROR_ADDRESSMODE |             |
+    */
+    public get wrapV() {
+        return this._wrapV;
+    }
+
+    public set wrapV(value: number) {
+        this._wrapV = value;
+    }
+
+    /**
+     * | Value | Type               | Description |
+     * | ----- | ------------------ | ----------- |
+     * | 0     | CLAMP_ADDRESSMODE  |             |
+     * | 1     | WRAP_ADDRESSMODE   |             |
+     * | 2     | MIRROR_ADDRESSMODE |             |
+     */
+    public wrapR = Constants.TEXTURE_WRAP_ADDRESSMODE;
+
+    /**
+     * With compliant hardware and browser (supporting anisotropic filtering)
+     * this defines the level of anisotropic filtering in the texture.
+     * The higher the better but the slower. This defaults to 4 as it seems to be the best tradeoff.
+     */
+    public anisotropicFilteringLevel = 4;
+
+    /**
+     * Define the current state of the loading sequence when in delayed load mode.
+     */
+    public delayLoadState = Constants.DELAYLOADSTATE_NONE;
+
+    /**
+     * How a texture is mapped.
+     * Unused in thin texture mode.
+     */
+    public get coordinatesMode(): number {
+        return 0;
+    }
+
+    /**
+     * Define if the texture is a cube texture or if false a 2d texture.
+     */
+    public get isCube(): boolean {
+        if (!this._texture) {
+            return false;
+        }
+
+        return this._texture.isCube;
+    }
+
+    public set isCube(value: boolean) {
+        if (!this._texture) {
+            return;
+        }
+
+        this._texture.isCube = value;
+    }
+
+    /**
+     * Define if the texture is a 3d texture (webgl 2) or if false a 2d texture.
+     */
+    public get is3D(): boolean {
+        if (!this._texture) {
+            return false;
+        }
+
+        return this._texture.is3D;
+    }
+
+    public set is3D(value: boolean) {
+        if (!this._texture) {
+            return;
+        }
+
+        this._texture.is3D = value;
+    }
+
+    /**
+     * Define if the texture is a 2d array texture (webgl 2) or if false a 2d texture.
+     */
+    public get is2DArray(): boolean {
+        if (!this._texture) {
+            return false;
+        }
+
+        return this._texture.is2DArray;
+    }
+
+    public set is2DArray(value: boolean) {
+        if (!this._texture) {
+            return;
+        }
+
+        this._texture.is2DArray = value;
+    }
+
+    /**
+     * Get the class name of the texture.
+     * @returns "ThinTexture"
+     */
+    public getClassName(): string {
+        return "ThinTexture";
+    }
+
+    /** @hidden */
+    public _texture: Nullable<InternalTexture> = null;
+
+    protected _engine: Nullable<ThinEngine> = null;
+
+    private _cachedSize: ISize = Size.Zero();
+    private _cachedBaseSize: ISize = Size.Zero();
+
+    /**
+     * Instantiates a new ThinTexture.
+     * Base class of all the textures in babylon.
+     * This can be used as an internal texture wrapper in ThinEngine to benefit from the cache
+     * @param internalTexture Define the internalTexture to wrap
+     */
+    constructor(internalTexture: Nullable<InternalTexture>) {
+        this._texture = internalTexture;
+        if (this._texture) {
+            this._engine = this._texture.getEngine();
+        }
+    }
+
+    /**
+     * Get if the texture is ready to be used (downloaded, converted, mip mapped...).
+     * @returns true if fully ready
+     */
+    public isReady(): boolean {
+        if (this.delayLoadState === Constants.DELAYLOADSTATE_NOTLOADED) {
+            this.delayLoad();
+            return false;
+        }
+
+        if (this._texture) {
+            return this._texture.isReady;
+        }
+
+        return false;
+    }
+
+    /**
+     * Triggers the load sequence in delayed load mode.
+     */
+    public delayLoad(): void {
+    }
+
+    /**
+     * Get the underlying lower level texture from Babylon.
+     * @returns the insternal texture
+     */
+    public getInternalTexture(): Nullable<InternalTexture> {
+        return this._texture;
+    }
+
+    /**
+     * Get the size of the texture.
+     * @returns the texture size.
+     */
+    public getSize(): ISize {
+        if (this._texture) {
+            if (this._texture.width) {
+                this._cachedSize.width = this._texture.width;
+                this._cachedSize.height = this._texture.height;
+                return this._cachedSize;
+            }
+
+            if (this._texture._size) {
+                this._cachedSize.width = this._texture._size;
+                this._cachedSize.height = this._texture._size;
+                return this._cachedSize;
+            }
+        }
+
+        return this._cachedSize;
+    }
+
+    /**
+     * Get the base size of the texture.
+     * It can be different from the size if the texture has been resized for POT for instance
+     * @returns the base size
+     */
+    public getBaseSize(): ISize {
+        if (!this.isReady() || !this._texture) {
+            this._cachedBaseSize.width = 0;
+            this._cachedBaseSize.height = 0;
+            return this._cachedBaseSize;
+        }
+
+        if (this._texture._size) {
+            this._cachedBaseSize.width = this._texture._size;
+            this._cachedBaseSize.height = this._texture._size;
+            return this._cachedBaseSize;
+        }
+
+        this._cachedBaseSize.width = this._texture.baseWidth;
+        this._cachedBaseSize.height = this._texture.baseHeight;
+        return this._cachedBaseSize;
+    }
+
+    /**
+     * Update the sampling mode of the texture.
+     * Default is Trilinear mode.
+     *
+     * | Value | Type               | Description |
+     * | ----- | ------------------ | ----------- |
+     * | 1     | NEAREST_SAMPLINGMODE or NEAREST_NEAREST_MIPLINEAR  | Nearest is: mag = nearest, min = nearest, mip = linear |
+     * | 2     | BILINEAR_SAMPLINGMODE or LINEAR_LINEAR_MIPNEAREST | Bilinear is: mag = linear, min = linear, mip = nearest |
+     * | 3     | TRILINEAR_SAMPLINGMODE or LINEAR_LINEAR_MIPLINEAR | Trilinear is: mag = linear, min = linear, mip = linear |
+     * | 4     | NEAREST_NEAREST_MIPNEAREST |             |
+     * | 5    | NEAREST_LINEAR_MIPNEAREST |             |
+     * | 6    | NEAREST_LINEAR_MIPLINEAR |             |
+     * | 7    | NEAREST_LINEAR |             |
+     * | 8    | NEAREST_NEAREST |             |
+     * | 9   | LINEAR_NEAREST_MIPNEAREST |             |
+     * | 10   | LINEAR_NEAREST_MIPLINEAR |             |
+     * | 11   | LINEAR_LINEAR |             |
+     * | 12   | LINEAR_NEAREST |             |
+     *
+     *    > _mag_: magnification filter (close to the viewer)
+     *    > _min_: minification filter (far from the viewer)
+     *    > _mip_: filter used between mip map levels
+     *@param samplingMode Define the new sampling mode of the texture
+     */
+    public updateSamplingMode(samplingMode: number): void {
+        if (this._texture && this._engine) {
+            this._engine.updateTextureSamplingMode(samplingMode, this._texture);
+        }
+    }
+
+    /**
+     * Release and destroy the underlying lower level texture aka internalTexture.
+     */
+    public releaseInternalTexture(): void {
+        if (this._texture) {
+            this._texture.dispose();
+            this._texture = null;
+        }
+    }
+
+    /**
+     * Dispose the texture and release its associated resources.
+     */
+    public dispose(): void {
+        if (this._texture) {
+            this.releaseInternalTexture();
+            this._engine = null;
+        }
+    }
+}

+ 3 - 3
src/Materials/effect.ts

@@ -13,7 +13,7 @@ import { IEffectFallbacks } from './iEffectFallbacks';
 
 declare type Engine = import("../Engines/engine").Engine;
 declare type InternalTexture = import("../Materials/Textures/internalTexture").InternalTexture;
-declare type BaseTexture = import("../Materials/Textures/baseTexture").BaseTexture;
+declare type ThinTexture = import("../Materials/Textures/thinTexture").ThinTexture;
 declare type RenderTargetTexture = import("../Materials/Textures/renderTargetTexture").RenderTargetTexture;
 declare type PostProcess = import("../PostProcesses/postProcess").PostProcess;
 
@@ -809,7 +809,7 @@ export class Effect implements IDisposable {
      * @param channel Name of the sampler variable.
      * @param texture Texture to set.
      */
-    public setTexture(channel: string, texture: Nullable<BaseTexture>): void {
+    public setTexture(channel: string, texture: Nullable<ThinTexture>): void {
         this._engine.setTexture(this._samplers[channel], this._uniforms[channel], texture);
     }
 
@@ -827,7 +827,7 @@ export class Effect implements IDisposable {
      * @param channel Name of the variable.
      * @param textures Textures to set.
      */
-    public setTextureArray(channel: string, textures: BaseTexture[]): void {
+    public setTextureArray(channel: string, textures: ThinTexture[]): void {
         let exName = channel + "Ex";
         if (this._samplerList.indexOf(exName + "0") === -1) {
             const initialPos = this._samplerList.indexOf(channel);

+ 3 - 1
src/Meshes/abstractMesh.ts

@@ -36,6 +36,7 @@ declare type Ray = import("../Culling/ray").Ray;
 declare type Collider = import("../Collisions/collider").Collider;
 declare type TrianglePickingPredicate = import("../Culling/ray").TrianglePickingPredicate;
 declare type RenderingGroup = import("../Rendering/renderingGroup").RenderingGroup;
+declare type IEdgesRendererOptions = import("../Rendering/edgesRenderer").IEdgesRendererOptions;
 
 /** @hidden */
 class _FacetDataStorage {
@@ -2227,10 +2228,11 @@ export class AbstractMesh extends TransformNode implements IDisposable, ICullabl
      * This mode makes the mesh edges visible
      * @param epsilon defines the maximal distance between two angles to detect a face
      * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
+     * @param options options to the edge renderer
      * @returns the currentAbstractMesh
      * @see https://www.babylonjs-playground.com/#19O9TU#0
      */
-    enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): AbstractMesh {
+    enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean, options?: IEdgesRendererOptions): AbstractMesh {
         throw _DevTools.WarnImport("EdgesRenderer");
     }
 

+ 9 - 9
src/Misc/screenshotTools.ts

@@ -123,15 +123,6 @@ export class ScreenshotTools {
             Logger.Error("Invalid 'size' parameter !");
             return;
         }
-
-        var scene = camera.getScene();
-        var previousCamera: Nullable<Camera> = null;
-
-        if (scene.activeCamera !== camera) {
-            previousCamera = scene.activeCamera;
-            scene.activeCamera = camera;
-        }
-
         var renderCanvas = engine.getRenderingCanvas();
         if (!renderCanvas) {
             Logger.Error("No rendering canvas found !");
@@ -140,8 +131,16 @@ export class ScreenshotTools {
 
         var originalSize = { width: renderCanvas.width, height: renderCanvas.height };
         engine.setSize(width, height);
+
+        var scene = camera.getScene();
         scene.render();
 
+        var previousCamera: Nullable<Camera> = null;
+        if (scene.activeCamera !== camera) {
+            previousCamera = scene.activeCamera;
+            scene.activeCamera = camera;
+        }
+
         // At this point size can be a number, or an object (according to engine.prototype.createRenderTargetTexture method)
         var texture = new RenderTargetTexture("screenShot", targetTextureSize, scene, false, false, Constants.TEXTURETYPE_UNSIGNED_INT, false, Texture.NEAREST_SAMPLINGMODE, undefined, enableStencilBuffer);
         texture.renderList = null;
@@ -160,6 +159,7 @@ export class ScreenshotTools {
             if (previousCamera) {
                 scene.activeCamera = previousCamera;
             }
+
             engine.setSize(originalSize.width, originalSize.height);
             camera.getProjectionMatrix(true); // Force cache refresh;
         };

+ 16 - 1
src/Rendering/edgesRenderer.ts

@@ -1,4 +1,4 @@
-import { Nullable } from "../types";
+import { Immutable, Nullable } from "../types";
 import { VertexBuffer } from "../Meshes/buffer";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
@@ -206,6 +206,21 @@ export class EdgesRenderer implements IEdgesRenderer {
     /** Gets or sets a boolean indicating if the edgesRenderer is active */
     public isEnabled = true;
 
+    /** Gets the vertices generated by the edge renderer */
+    public get linesPositions(): Immutable<Array<number>> {
+        return this._linesPositions;
+    }
+
+    /** Gets the normals generated by the edge renderer */
+    public get linesNormals(): Immutable<Array<number>> {
+        return this._linesNormals;
+    }
+
+    /** Gets the indices generated by the edge renderer */
+    public get linesIndices(): Immutable<Array<number>> {
+        return this._linesIndices;
+    }
+
     /**
      * List of instances to render in case the source mesh has instances
      */

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

@@ -128,7 +128,7 @@
         luminanceOverAlpha += getLuminance(finalClearCoatScaled);
     #endif
 
-    #if defined(RADIANCEOVERALPHA) || defined(SPECULAROVERALPHA)
+    #if defined(RADIANCEOVERALPHA) || defined(SPECULAROVERALPHA) || defined(CLEARCOATOVERALPHA)
         alpha = saturate(alpha + luminanceOverAlpha * luminanceOverAlpha);
     #endif
 #endif

+ 23 - 115
src/Sprites/sprite.ts

@@ -5,31 +5,19 @@ import { ISpriteManager, SpriteManager } from "./spriteManager";
 import { Color4 } from '../Maths/math.color';
 import { Observable } from '../Misc/observable';
 import { IAnimatable } from '../Animations/animatable.interface';
+import { ThinSprite } from './thinSprite';
+
 declare type Animation = import("../Animations/animation").Animation;
 
 /**
  * Class used to represent a sprite
  * @see https://doc.babylonjs.com/babylon101/sprites
  */
-export class Sprite implements IAnimatable {
+export class Sprite extends ThinSprite implements IAnimatable {
     /** Gets or sets the current world position */
     public position: Vector3;
     /** Gets or sets the main color */
-    public color = new Color4(1.0, 1.0, 1.0, 1.0);
-    /** Gets or sets the width */
-    public width = 1.0;
-    /** Gets or sets the height */
-    public height = 1.0;
-    /** Gets or sets rotation angle */
-    public angle = 0;
-    /** Gets or sets the cell index in the sprite sheet */
-    public cellIndex: number;
-    /** Gets or sets the cell reference in the sprite sheet, uses sprite's filename when added to sprite sheet */
-    public cellRef: string;
-    /** Gets or sets a boolean indicating if UV coordinates should be inverted in U axis */
-    public invertU = false;
-    /** Gets or sets a boolean indicating if UV coordinates should be inverted in B axis */
-    public invertV = false;
+    public color: Color4;
     /** Gets or sets a boolean indicating that this sprite should be disposed after animation ends */
     public disposeWhenFinishedAnimating: boolean;
     /** Gets the list of attached animations */
@@ -39,38 +27,18 @@ export class Sprite implements IAnimatable {
     /** Gets or sets a boolean indicating that sprite texture alpha will be used for precise picking (false by default) */
     public useAlphaForPicking = false;
 
-    /** @hidden */
-    public _xOffset: number;
-    /** @hidden */
-    public _yOffset: number;
-    /** @hidden */
-    public _xSize: number;
-    /** @hidden */
-    public _ySize: number;
-
     /**
      * Gets or sets the associated action manager
      */
     public actionManager: Nullable<ActionManager>;
 
     /**
-    * An event triggered when the control has been disposed
-    */
-   public onDisposeObservable = new Observable<Sprite>();
+     * An event triggered when the control has been disposed
+     */
+    public onDisposeObservable = new Observable<Sprite>();
 
-    private _animationStarted = false;
-    private _loopAnimation = false;
-    private _fromIndex = 0;
-    private _toIndex = 0;
-    private _delay = 0;
-    private _direction = 1;
     private _manager: ISpriteManager;
-    private _time = 0;
     private _onAnimationEnd: Nullable<() => void> = null;
-    /**
-     * Gets or sets a boolean indicating if the sprite is visible (renderable). Default is true
-     */
-    public isVisible = true;
 
     /**
      * Gets or sets the sprite size
@@ -85,13 +53,6 @@ export class Sprite implements IAnimatable {
     }
 
     /**
-     * Returns a boolean indicating if the animation is started
-     */
-    public get animationStarted() {
-        return this._animationStarted;
-    }
-
-    /**
      * Gets or sets the unique id of the sprite
      */
     public uniqueId: number;
@@ -112,12 +73,13 @@ export class Sprite implements IAnimatable {
         /** defines the name */
         public name: string,
         manager: ISpriteManager) {
-        this._manager = manager;
-
-        this._manager.sprites.push(this);
+        super();
         this.uniqueId = this._manager.scene.getUniqueId();
-
+        this.color = new Color4(1.0, 1.0, 1.0, 1.0);
         this.position = Vector3.Zero();
+
+        this._manager = manager;
+        this._manager.sprites.push(this);
     }
 
     /**
@@ -129,39 +91,23 @@ export class Sprite implements IAnimatable {
     }
 
     /** Gets or sets the initial key for the animation (setting it will restart the animation)  */
-    public get fromIndex() {
-        return this._fromIndex;
-    }
-
     public set fromIndex(value: number) {
-        this.playAnimation(value, this._toIndex, this._loopAnimation, this._delay, this._onAnimationEnd);
+        this.playAnimation(value, this.toIndex, this.loopAnimation, this.delay, this._onAnimationEnd);
     }
 
     /** Gets or sets the end key for the animation (setting it will restart the animation)  */
-    public get toIndex() {
-        return this._toIndex;
-    }
-
     public set toIndex(value: number) {
-        this.playAnimation(this._fromIndex, value, this._loopAnimation, this._delay, this._onAnimationEnd);
+        this.playAnimation(this.fromIndex, value, this.loopAnimation, this.delay, this._onAnimationEnd);
     }
 
     /** Gets or sets a boolean indicating if the animation is looping (setting it will restart the animation)  */
-    public get loopAnimation() {
-        return this._loopAnimation;
-    }
-
     public set loopAnimation(value: boolean) {
-        this.playAnimation(this._fromIndex, this._toIndex, value, this._delay, this._onAnimationEnd);
+        this.playAnimation(this.fromIndex, this.toIndex, value, this.delay, this._onAnimationEnd);
     }
 
     /** Gets or sets the delay between cell changes (setting it will restart the animation)  */
-    public get delay() {
-        return Math.max(this._delay, 1);
-    }
-
     public set delay(value: number) {
-        this.playAnimation(this._fromIndex, this._toIndex, this._loopAnimation, value, this._onAnimationEnd);
+        this.playAnimation(this.fromIndex, this.toIndex, this.loopAnimation, value, this._onAnimationEnd);
     }
 
     /**
@@ -173,57 +119,19 @@ export class Sprite implements IAnimatable {
      * @param onAnimationEnd defines a callback to call when animation ends
      */
     public playAnimation(from: number, to: number, loop: boolean, delay: number, onAnimationEnd: Nullable<() => void> = null): void {
-        this._fromIndex = from;
-        this._toIndex = to;
-        this._loopAnimation = loop;
-        this._delay = delay || 1;
-        this._animationStarted = true;
-
-        if (from < to) {
-            this._direction = 1;
-        } else {
-            this._direction = -1;
-            this._toIndex = from;
-            this._fromIndex = to;
-        }
-
-        this.cellIndex = from;
-        this._time = 0;
-
         this._onAnimationEnd = onAnimationEnd;
-    }
 
-    /** Stops current animation (if any) */
-    public stopAnimation(): void {
-        this._animationStarted = false;
+        super.playAnimation(from, to, loop, delay, this._endAnimation);
     }
 
-    /** @hidden */
-    public _animate(deltaTime: number): void {
-        if (!this._animationStarted) {
-            return;
+    private _endAnimation = () => {
+        if (this._onAnimationEnd) {
+            this._onAnimationEnd();
         }
-
-        this._time += deltaTime;
-        if (this._time > this._delay) {
-            this._time = this._time % this._delay;
-            this.cellIndex += this._direction;
-            if (this._direction > 0 && this.cellIndex > this._toIndex || this._direction < 0 && this.cellIndex < this._fromIndex) {
-                if (this._loopAnimation) {
-                    this.cellIndex = this._direction > 0 ? this._fromIndex : this._toIndex;
-                } else {
-                    this.cellIndex = this._toIndex;
-                    this._animationStarted = false;
-                    if (this._onAnimationEnd) {
-                        this._onAnimationEnd();
-                    }
-                    if (this.disposeWhenFinishedAnimating) {
-                        this.dispose();
-                    }
-                }
-            }
+        if (this.disposeWhenFinishedAnimating) {
+            this.dispose();
         }
-    }
+    };
 
     /** Release associated resources */
     public dispose(): void {

+ 78 - 314
src/Sprites/spriteManager.ts

@@ -1,25 +1,22 @@
 import { IDisposable, Scene } from "../scene";
 import { Nullable } from "../types";
 import { Observable, Observer } from "../Misc/observable";
-import { Buffer } from "../Meshes/buffer";
-import { VertexBuffer } from "../Meshes/buffer";
 import { Vector3, TmpVectors } from "../Maths/math.vector";
 import { Sprite } from "./sprite";
 import { SpriteSceneComponent } from "./spriteSceneComponent";
 import { PickingInfo } from "../Collisions/pickingInfo";
 import { Camera } from "../Cameras/camera";
 import { Texture } from "../Materials/Textures/texture";
-import { Effect } from "../Materials/effect";
-import { Material } from "../Materials/material";
 import { SceneComponentConstants } from "../sceneComponent";
-import { Constants } from "../Engines/constants";
 import { Logger } from "../Misc/logger";
 
 import "../Shaders/sprites.fragment";
 import "../Shaders/sprites.vertex";
-import { DataBuffer } from '../Meshes/dataBuffer';
 import { Engine } from '../Engines/engine';
 import { WebRequest } from '../Misc/webRequest';
+import { SpriteRenderer } from './spriteRenderer';
+import { ThinSprite } from './thinSprite';
+import { ISize } from '../Maths/math.size';
 declare type Ray = import("../Culling/ray").Ray;
 
 /**
@@ -111,33 +108,14 @@ export class SpriteManager implements ISpriteManager {
     public renderingGroupId = 0;
     /** Gets or sets camera layer mask */
     public layerMask: number = 0x0FFFFFFF;
-    /** Gets or sets a boolean indicating if the manager must consider scene fog when rendering */
-    public fogEnabled = true;
     /** Gets or sets a boolean indicating if the sprites are pickable */
     public isPickable = false;
-    /** Defines the default width of a cell in the spritesheet */
-    public cellWidth: number;
-    /** Defines the default height of a cell in the spritesheet */
-    public cellHeight: number;
-
-    /** Associative array from JSON sprite data file */
-    private _cellData: any;
-    /** Array of sprite names from JSON sprite data file */
-    private _spriteMap: Array<string>;
-    /** True when packed cell data from JSON file is ready*/
-    private _packedAndReady: boolean = false;
-
-    private _textureContent: Nullable<Uint8Array>;
-
-    private _useInstancing = false;
 
     /**
     * An event triggered when the manager is disposed.
     */
     public onDisposeObservable = new Observable<SpriteManager>();
 
-    private _onDisposeObserver: Nullable<Observer<SpriteManager>>;
-
     /**
      * Callback called when the manager is disposed
      */
@@ -148,23 +126,6 @@ export class SpriteManager implements ISpriteManager {
         this._onDisposeObserver = this.onDisposeObservable.add(callback);
     }
 
-    private _capacity: number;
-    private _fromPacked: boolean;
-    private _spriteTexture: Texture;
-    private _epsilon: number;
-
-    private _scene: Scene;
-
-    private _vertexData: Float32Array;
-    private _buffer: Buffer;
-    private _vertexBuffers: { [key: string]: VertexBuffer } = {};
-    private _spriteBuffer: Nullable<Buffer>;
-    private _indexBuffer: DataBuffer;
-    private _effectBase: Effect;
-    private _effectFog: Effect;
-
-    private _vertexBufferSize: number;
-
     /**
      * Gets or sets the unique id of the sprite
      */
@@ -188,32 +149,56 @@ export class SpriteManager implements ISpriteManager {
      * Gets the capacity of the manager
      */
     public get capacity() {
-        return this._capacity;
+        return this._spriteRenderer.capacity;
     }
 
     /**
      * Gets or sets the spritesheet texture
      */
     public get texture(): Texture {
-        return this._spriteTexture;
+        return this._spriteRenderer.texture as Texture;
     }
-
     public set texture(value: Texture) {
-        this._spriteTexture = value;
-        this._spriteTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
-        this._spriteTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
+        value.wrapU = Texture.CLAMP_ADDRESSMODE;
+        value.wrapV = Texture.CLAMP_ADDRESSMODE;
+        this._spriteRenderer.texture = value;
         this._textureContent = null;
     }
 
-    private _blendMode = Constants.ALPHA_COMBINE;
+    /** Defines the default width of a cell in the spritesheet */
+    public get cellWidth(): number {
+        return this._spriteRenderer.cellWidth;
+    }
+    public set cellWidth(value: number) {
+        this._spriteRenderer.cellWidth = value;
+    }
+
+    /** Defines the default height of a cell in the spritesheet */
+    public get cellHeight(): number {
+        return this._spriteRenderer.cellHeight;
+    }
+    public set cellHeight(value: number) {
+        this._spriteRenderer.cellHeight = value;
+    }
+
+    /** Gets or sets a boolean indicating if the manager must consider scene fog when rendering */
+    public get fogEnabled(): boolean {
+        return this._spriteRenderer.fogEnabled;
+    }
+    public set fogEnabled(value: boolean) {
+        this._spriteRenderer.fogEnabled = value;
+    }
+
     /**
      * Blend mode use to render the particle, it can be any of
      * the static Constants.ALPHA_x properties provided in this class.
      * Default value is Constants.ALPHA_COMBINE
      */
-    public get blendMode() { return this._blendMode; }
+    public get blendMode() {
+        return this._spriteRenderer.blendMode;
+    }
     public set blendMode(blendMode: number) {
-        this._blendMode = blendMode;
+        this._spriteRenderer.blendMode = blendMode;
     }
 
     /** Disables writing to the depth buffer when rendering the sprites.
@@ -222,6 +207,18 @@ export class SpriteManager implements ISpriteManager {
     */
     public disableDepthWrite: boolean = false;
 
+    private _spriteRenderer: SpriteRenderer;
+    /** Associative array from JSON sprite data file */
+    private _cellData: any;
+    /** Array of sprite names from JSON sprite data file */
+    private _spriteMap: Array<string>;
+    /** True when packed cell data from JSON file is ready*/
+    private _packedAndReady: boolean = false;
+    private _textureContent: Nullable<Uint8Array>;
+    private _onDisposeObserver: Nullable<Observer<SpriteManager>>;
+    private _fromPacked: boolean;
+    private _scene: Scene;
+
     /**
      * Creates a new sprite manager
      * @param name defines the manager's name
@@ -246,15 +243,8 @@ export class SpriteManager implements ISpriteManager {
         if (!scene._getComponent(SceneComponentConstants.NAME_SPRITE)) {
             scene._addComponent(new SpriteSceneComponent(scene));
         }
-        this._capacity = capacity;
         this._fromPacked = fromPacked;
 
-        if (imgUrl) {
-            this._spriteTexture = new Texture(imgUrl, scene, true, false, samplingMode);
-            this._spriteTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
-            this._spriteTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
-        }
-
         if (cellSize.width && cellSize.height) {
             this.cellWidth = cellSize.width;
             this.cellHeight = cellSize.height;
@@ -265,74 +255,18 @@ export class SpriteManager implements ISpriteManager {
             return;
         }
 
-        this._epsilon = epsilon;
         this._scene = scene;
         this._scene.spriteManagers.push(this);
         this.uniqueId = this.scene.getUniqueId();
-        const engine = this._scene.getEngine();
-        this._useInstancing = engine.getCaps().instancedArrays;
-
-        if (!this._useInstancing) {
-            var indices = [];
-            var index = 0;
-            for (var count = 0; count < capacity; count++) {
-                indices.push(index);
-                indices.push(index + 1);
-                indices.push(index + 2);
-                indices.push(index);
-                indices.push(index + 2);
-                indices.push(index + 3);
-                index += 4;
-            }
-
-            this._indexBuffer = engine.createIndexBuffer(indices);
-        } else {
-        }
-
-        // VBO
-        // 18 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellLeft, cellTop, cellWidth, cellHeight, color r, color g, color b, color a)
-        // 16 when using instances
-        this._vertexBufferSize = this._useInstancing ? 16 : 18;
-        this._vertexData = new Float32Array(capacity * this._vertexBufferSize * (this._useInstancing ? 1 : 4));
-        this._buffer = new Buffer(engine, this._vertexData, true, this._vertexBufferSize);
-
-        var positions = this._buffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 4, this._vertexBufferSize, this._useInstancing);
-        var options = this._buffer.createVertexBuffer("options", 4, 2, this._vertexBufferSize, this._useInstancing);
 
-        let offset = 6;
-        var offsets: VertexBuffer;
-
-        if (this._useInstancing) {
-            var spriteData = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
-            this._spriteBuffer = new Buffer(engine, spriteData, false, 2);
-            offsets = this._spriteBuffer.createVertexBuffer("offsets", 0, 2);
-        } else {
-            offsets = this._buffer.createVertexBuffer("offsets", offset, 2, this._vertexBufferSize, this._useInstancing);
-            offset += 2;
+        const engine = this._scene.getEngine();
+        this._spriteRenderer = new SpriteRenderer(engine, capacity, epsilon, scene);
+        this._spriteRenderer.cellWidth = this.cellWidth;
+        this._spriteRenderer.cellHeight = this.cellHeight;
+        if (imgUrl) {
+            this._spriteRenderer.texture = new Texture(imgUrl, scene, true, false, samplingMode);
         }
 
-        var inverts = this._buffer.createVertexBuffer("inverts", offset, 2, this._vertexBufferSize, this._useInstancing);
-        var cellInfo = this._buffer.createVertexBuffer("cellInfo", offset + 2, 4, this._vertexBufferSize, this._useInstancing);
-        var colors = this._buffer.createVertexBuffer(VertexBuffer.ColorKind, offset + 6, 4, this._vertexBufferSize, this._useInstancing);
-
-        this._vertexBuffers[VertexBuffer.PositionKind] = positions;
-        this._vertexBuffers["options"] = options;
-        this._vertexBuffers["offsets"] = offsets;
-        this._vertexBuffers["inverts"] = inverts;
-        this._vertexBuffers["cellInfo"] = cellInfo;
-        this._vertexBuffers[VertexBuffer.ColorKind] = colors;
-
-        // Effects
-        this._effectBase = this._scene.getEngine().createEffect("sprites",
-            [VertexBuffer.PositionKind, "options", "offsets", "inverts", "cellInfo", VertexBuffer.ColorKind],
-            ["view", "projection", "textureInfos", "alphaTest"],
-            ["diffuseSampler"], "");
-
-        this._effectFog = this._scene.getEngine().createEffect("sprites",
-            [VertexBuffer.PositionKind, "options", "offsets", "inverts", "cellInfo", VertexBuffer.ColorKind],
-            ["view", "projection", "textureInfos", "alphaTest", "vFogInfos", "vFogColor"],
-            ["diffuseSampler"], "#define FOG");
-
         if (this._fromPacked) {
             this._makePacked(imgUrl, spriteJSON);
         }
@@ -416,97 +350,15 @@ export class SpriteManager implements ISpriteManager {
         }
     }
 
-    private _appendSpriteVertex(index: number, sprite: Sprite, offsetX: number, offsetY: number, baseSize: any): void {
-        var arrayOffset = index * this._vertexBufferSize;
-
-        if (offsetX === 0) {
-            offsetX = this._epsilon;
-        }
-        else if (offsetX === 1) {
-            offsetX = 1 - this._epsilon;
-        }
-
-        if (offsetY === 0) {
-            offsetY = this._epsilon;
-        }
-        else if (offsetY === 1) {
-            offsetY = 1 - this._epsilon;
-        }
-
-        // Positions
-        this._vertexData[arrayOffset] = sprite.position.x;
-        this._vertexData[arrayOffset + 1] = sprite.position.y;
-        this._vertexData[arrayOffset + 2] = sprite.position.z;
-        this._vertexData[arrayOffset + 3] = sprite.angle;
-        // Options
-        this._vertexData[arrayOffset + 4] = sprite.width;
-        this._vertexData[arrayOffset + 5] = sprite.height;
-
-        if (!this._useInstancing) {
-            this._vertexData[arrayOffset + 6] = offsetX;
-            this._vertexData[arrayOffset + 7] = offsetY;
-        } else {
-            arrayOffset -= 2;
-        }
-
-        // Inverts according to Right Handed
-        if (this._scene.useRightHandedSystem) {
-            this._vertexData[arrayOffset + 8] = sprite.invertU ? 0 : 1;
-        }
-        else {
-            this._vertexData[arrayOffset + 8] = sprite.invertU ? 1 : 0;
-        }
-
-        this._vertexData[arrayOffset + 9] = sprite.invertV ? 1 : 0;
-        // CellIfo
-        if (this._packedAndReady) {
-            if (!sprite.cellRef) {
-                sprite.cellIndex = 0;
-            }
-            let num = sprite.cellIndex;
-            if (typeof (num) === "number" && isFinite(num) && Math.floor(num) === num) {
-                sprite.cellRef = this._spriteMap[sprite.cellIndex];
-            }
-            sprite._xOffset = this._cellData[sprite.cellRef].frame.x / baseSize.width;
-            sprite._yOffset = this._cellData[sprite.cellRef].frame.y / baseSize.height;
-            sprite._xSize = this._cellData[sprite.cellRef].frame.w;
-            sprite._ySize = this._cellData[sprite.cellRef].frame.h;
-            this._vertexData[arrayOffset + 10] = sprite._xOffset;
-            this._vertexData[arrayOffset + 11] = sprite._yOffset;
-            this._vertexData[arrayOffset + 12] = sprite._xSize / baseSize.width;
-            this._vertexData[arrayOffset + 13] = sprite._ySize / baseSize.height;
-        }
-        else {
-            if (!sprite.cellIndex) {
-                sprite.cellIndex = 0;
-            }
-            var rowSize = baseSize.width / this.cellWidth;
-            var offset = (sprite.cellIndex / rowSize) >> 0;
-            sprite._xOffset = (sprite.cellIndex - offset * rowSize) * this.cellWidth / baseSize.width;
-            sprite._yOffset = offset * this.cellHeight / baseSize.height;
-            sprite._xSize = this.cellWidth;
-            sprite._ySize = this.cellHeight;
-            this._vertexData[arrayOffset + 10] = sprite._xOffset;
-            this._vertexData[arrayOffset + 11] = sprite._yOffset;
-            this._vertexData[arrayOffset + 12] = this.cellWidth / baseSize.width;
-            this._vertexData[arrayOffset + 13] = this.cellHeight / baseSize.height;
-        }
-        // Color
-        this._vertexData[arrayOffset + 14] = sprite.color.r;
-        this._vertexData[arrayOffset + 15] = sprite.color.g;
-        this._vertexData[arrayOffset + 16] = sprite.color.b;
-        this._vertexData[arrayOffset + 17] = sprite.color.a;
-    }
-
     private _checkTextureAlpha(sprite: Sprite, ray: Ray, distance: number, min: Vector3, max: Vector3) {
-        if (!sprite.useAlphaForPicking || !this._spriteTexture) {
+        if (!sprite.useAlphaForPicking || !this.texture) {
             return true;
         }
 
-        let textureSize = this._spriteTexture.getSize();
+        let textureSize = this.texture.getSize();
         if (!this._textureContent) {
             this._textureContent = new Uint8Array(textureSize.width * textureSize.height * 4);
-            this._spriteTexture.readPixels(0, 0, this._textureContent);
+            this.texture.readPixels(0, 0, this._textureContent);
         }
 
         let contactPoint = TmpVectors.Vector3[0];
@@ -542,7 +394,7 @@ export class SpriteManager implements ISpriteManager {
      * @returns null if no hit or a PickingInfo
      */
     public intersects(ray: Ray, camera: Camera, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean): Nullable<PickingInfo> {
-        var count = Math.min(this._capacity, this.sprites.length);
+        var count = Math.min(this.capacity, this.sprites.length);
         var min = Vector3.Zero();
         var max = Vector3.Zero();
         var distance = Number.MAX_VALUE;
@@ -620,7 +472,7 @@ export class SpriteManager implements ISpriteManager {
      * @returns null if no hit or a PickingInfo array
      */
     public multiIntersects(ray: Ray, camera: Camera, predicate?: (sprite: Sprite) => boolean): Nullable<PickingInfo[]> {
-        var count = Math.min(this._capacity, this.sprites.length);
+        var count = Math.min(this.capacity, this.sprites.length);
         var min = Vector3.Zero();
         var max = Vector3.Zero();
         var distance: number;
@@ -683,129 +535,41 @@ export class SpriteManager implements ISpriteManager {
      */
     public render(): void {
         // Check
-        if (!this._effectBase.isReady() || !this._effectFog.isReady() || !this._spriteTexture
-            || !this._spriteTexture.isReady() || !this.sprites.length) {
-            return;
-        }
-
         if (this._fromPacked  && (!this._packedAndReady || !this._spriteMap || !this._cellData)) {
             return;
         }
 
         var engine = this._scene.getEngine();
-        var baseSize = this._spriteTexture.getBaseSize();
-
-        // Sprites
         var deltaTime = engine.getDeltaTime();
-        var max = Math.min(this._capacity, this.sprites.length);
-
-        var offset = 0;
-        let noSprite = true;
-        for (var index = 0; index < max; index++) {
-            var sprite = this.sprites[index];
-            if (!sprite || !sprite.isVisible) {
-                continue;
-            }
-
-            noSprite = false;
-            sprite._animate(deltaTime);
-
-            this._appendSpriteVertex(offset++, sprite, 0, 0, baseSize);
-            if (!this._useInstancing) {
-                this._appendSpriteVertex(offset++, sprite, 1, 0, baseSize);
-                this._appendSpriteVertex(offset++, sprite, 1, 1, baseSize);
-                this._appendSpriteVertex(offset++, sprite, 0, 1, baseSize);
-            }
-        }
-
-        if (noSprite) {
-            return;
-        }
-
-        this._buffer.update(this._vertexData);
-
-        // Render
-        var effect = this._effectBase;
-
-        if (this._scene.fogEnabled && this._scene.fogMode !== Scene.FOGMODE_NONE && this.fogEnabled) {
-            effect = this._effectFog;
-        }
-
-        engine.enableEffect(effect);
-
-        var viewMatrix = this._scene.getViewMatrix();
-        effect.setTexture("diffuseSampler", this._spriteTexture);
-        effect.setMatrix("view", viewMatrix);
-        effect.setMatrix("projection", this._scene.getProjectionMatrix());
-
-        // Fog
-        if (this._scene.fogEnabled && this._scene.fogMode !== Scene.FOGMODE_NONE && this.fogEnabled) {
-            effect.setFloat4("vFogInfos", this._scene.fogMode, this._scene.fogStart, this._scene.fogEnd, this._scene.fogDensity);
-            effect.setColor3("vFogColor", this._scene.fogColor);
-        }
-
-        // VBOs
-        engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
-
-        // Handle Right Handed
-        const culling = engine.depthCullingState.cull || true;
-        const zOffset = engine.depthCullingState.zOffset;
-        if (this._scene.useRightHandedSystem) {
-            engine.setState(culling, zOffset, false, false);
+        if (this._packedAndReady) {
+            this._spriteRenderer.render(this.sprites, deltaTime, this._scene.getViewMatrix(), this._scene.getProjectionMatrix(), this._customUpdate);
         }
-
-        // Draw order
-        engine.setDepthFunctionToLessOrEqual();
-        if (!this.disableDepthWrite) {
-            effect.setBool("alphaTest", true);
-            engine.setColorWrite(false);
-            if (this._useInstancing) {
-                engine.drawArraysType(Constants.MATERIAL_TriangleFanDrawMode, 0, 4, offset);
-            } else {
-                engine.drawElementsType(Material.TriangleFillMode, 0, (offset / 4) * 6);
-            }
-            engine.setColorWrite(true);
-            effect.setBool("alphaTest", false);
+        else {
+            this._spriteRenderer.render(this.sprites, deltaTime, this._scene.getViewMatrix(), this._scene.getProjectionMatrix());
         }
+    }
 
-        engine.setAlphaMode(this._blendMode);
-        if (this._useInstancing) {
-            engine.drawArraysType(Constants.MATERIAL_TriangleFanDrawMode, 0, 4, offset);
-        } else {
-            engine.drawElementsType(Material.TriangleFillMode, 0, (offset / 4) * 6);
+    private _customUpdate = (sprite: ThinSprite, baseSize: ISize): void => {
+        if (!sprite.cellRef) {
+            sprite.cellIndex = 0;
         }
-        engine.setAlphaMode(Constants.ALPHA_DISABLE);
-
-        // Restore Right Handed
-        if (this._scene.useRightHandedSystem) {
-            engine.setState(culling, zOffset, false, true);
+        let num = sprite.cellIndex;
+        if (typeof (num) === "number" && isFinite(num) && Math.floor(num) === num) {
+            sprite.cellRef = this._spriteMap[sprite.cellIndex];
         }
-
-        engine.unbindInstanceAttributes();
-    }
+        sprite._xOffset = this._cellData[sprite.cellRef].frame.x / baseSize.width;
+        sprite._yOffset = this._cellData[sprite.cellRef].frame.y / baseSize.height;
+        sprite._xSize = this._cellData[sprite.cellRef].frame.w;
+        sprite._ySize = this._cellData[sprite.cellRef].frame.h;
+    };
 
     /**
      * Release associated resources
      */
     public dispose(): void {
-        if (this._buffer) {
-            this._buffer.dispose();
-            (<any>this._buffer) = null;
-        }
-
-        if (this._spriteBuffer) {
-            this._spriteBuffer.dispose();
-            (<any>this._spriteBuffer) = null;
-        }
-
-        if (this._indexBuffer) {
-            this._scene.getEngine()._releaseBuffer(this._indexBuffer);
-            (<any>this._indexBuffer) = null;
-        }
-
-        if (this._spriteTexture) {
-            this._spriteTexture.dispose();
-            (<any>this._spriteTexture) = null;
+        if (this._spriteRenderer) {
+            this._spriteRenderer.dispose();
+            (<any>this._spriteRenderer) = null;
         }
 
         this._textureContent = null;

+ 396 - 0
src/Sprites/spriteRenderer.ts

@@ -0,0 +1,396 @@
+import { Nullable } from "../types";
+import { Constants } from "../Engines/constants";
+import { IMatrixLike } from '../Maths/math.like';
+import { ThinEngine } from "../Engines/thinEngine";
+import { DataBuffer } from "../Meshes/dataBuffer";
+import { Buffer, VertexBuffer } from "../Meshes/buffer";
+import { Effect } from "../Materials/effect";
+import { ThinSprite } from './thinSprite';
+import { ISize } from '../Maths/math.size';
+
+declare type ThinTexture = import("../Materials/Textures/thinTexture").ThinTexture;
+declare type Scene = import("../scene").Scene;
+
+import "../Engines/Extensions/engine.alpha";
+import "../Engines/Extensions/engine.dynamicBuffer";
+
+import "../Shaders/sprites.fragment";
+import "../Shaders/sprites.vertex";
+
+/**
+ * Class used to render sprites.
+ *
+ * It can be used either to render Sprites or ThinSriptes with ThinEngine only.
+ */
+export class SpriteRenderer {
+    /**
+     * Defines the texture of the spritesheet
+     */
+    public texture: Nullable<ThinTexture>;
+
+    /**
+     * Defines the default width of a cell in the spritesheet
+     */
+    public cellWidth: number;
+
+    /**
+     * Defines the default height of a cell in the spritesheet
+     */
+    public cellHeight: number;
+
+    /**
+     * Blend mode use to render the particle, it can be any of
+     * the static Constants.ALPHA_x properties provided in this class.
+     * Default value is Constants.ALPHA_COMBINE
+     */
+    public blendMode = Constants.ALPHA_COMBINE;
+
+    /**
+     * Gets or sets a boolean indicating if alpha mode is automatically
+     * reset.
+     */
+    public autoResetAlpha = true;
+
+    /**
+     * Disables writing to the depth buffer when rendering the sprites.
+     * It can be handy to disable depth writing when using textures without alpha channel
+     * and setting some specific blend modes.
+     */
+    public disableDepthWrite: boolean = false;
+
+    /**
+     * Gets or sets a boolean indicating if the manager must consider scene fog when rendering
+     */
+    public fogEnabled = true;
+
+    /**
+     * Gets the capacity of the manager
+     */
+    public get capacity() {
+        return this._capacity;
+    }
+
+    private readonly _engine: ThinEngine;
+    private readonly _useVAO: boolean = false;
+    private readonly _useInstancing: boolean = false;
+    private readonly _scene: Nullable<Scene>;
+
+    private readonly _capacity: number;
+    private readonly _epsilon: number;
+
+    private _vertexBufferSize: number;
+    private _vertexData: Float32Array;
+    private _buffer: Buffer;
+    private _vertexBuffers: { [key: string]: VertexBuffer } = {};
+    private _spriteBuffer: Nullable<Buffer>;
+    private _indexBuffer: DataBuffer;
+    private _effectBase: Effect;
+    private _effectFog: Effect;
+    private _vertexArrayObject: WebGLVertexArrayObject;
+
+    /**
+     * Creates a new sprite Renderer
+     * @param engine defines the engine the renderer works with
+     * @param capacity defines the maximum allowed number of sprites
+     * @param epsilon defines the epsilon value to align texture (0.01 by default)
+     * @param scene defines the hosting scene
+     */
+    constructor(
+        engine: ThinEngine,
+        capacity: number,
+        epsilon: number = 0.01,
+        scene: Nullable<Scene> = null) {
+
+        this._capacity = capacity;
+        this._epsilon = epsilon;
+
+        this._engine = engine;
+        this._useInstancing = engine.getCaps().instancedArrays;
+        this._useVAO = engine.getCaps().vertexArrayObject && !engine.disableVertexArrayObjects;
+        this._scene = scene;
+
+        if (!this._useInstancing) {
+            const indices = [];
+            let index = 0;
+            for (let count = 0; count < capacity; count++) {
+                indices.push(index);
+                indices.push(index + 1);
+                indices.push(index + 2);
+                indices.push(index);
+                indices.push(index + 2);
+                indices.push(index + 3);
+                index += 4;
+            }
+
+            this._indexBuffer = engine.createIndexBuffer(indices);
+        }
+
+        // VBO
+        // 18 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellLeft, cellTop, cellWidth, cellHeight, color r, color g, color b, color a)
+        // 16 when using instances
+        this._vertexBufferSize = this._useInstancing ? 16 : 18;
+        this._vertexData = new Float32Array(capacity * this._vertexBufferSize * (this._useInstancing ? 1 : 4));
+        this._buffer = new Buffer(engine, this._vertexData, true, this._vertexBufferSize);
+
+        const positions = this._buffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 4, this._vertexBufferSize, this._useInstancing);
+        const options = this._buffer.createVertexBuffer("options", 4, 2, this._vertexBufferSize, this._useInstancing);
+
+        let offset = 6;
+        let offsets: VertexBuffer;
+
+        if (this._useInstancing) {
+            var spriteData = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
+            this._spriteBuffer = new Buffer(engine, spriteData, false, 2);
+            offsets = this._spriteBuffer.createVertexBuffer("offsets", 0, 2);
+        } else {
+            offsets = this._buffer.createVertexBuffer("offsets", offset, 2, this._vertexBufferSize, this._useInstancing);
+            offset += 2;
+        }
+
+        const inverts = this._buffer.createVertexBuffer("inverts", offset, 2, this._vertexBufferSize, this._useInstancing);
+        const cellInfo = this._buffer.createVertexBuffer("cellInfo", offset + 2, 4, this._vertexBufferSize, this._useInstancing);
+        const colors = this._buffer.createVertexBuffer(VertexBuffer.ColorKind, offset + 6, 4, this._vertexBufferSize, this._useInstancing);
+
+        this._vertexBuffers[VertexBuffer.PositionKind] = positions;
+        this._vertexBuffers["options"] = options;
+        this._vertexBuffers["offsets"] = offsets;
+        this._vertexBuffers["inverts"] = inverts;
+        this._vertexBuffers["cellInfo"] = cellInfo;
+        this._vertexBuffers[VertexBuffer.ColorKind] = colors;
+
+        // Effects
+        this._effectBase = this._engine.createEffect("sprites",
+            [VertexBuffer.PositionKind, "options", "offsets", "inverts", "cellInfo", VertexBuffer.ColorKind],
+            ["view", "projection", "textureInfos", "alphaTest"],
+            ["diffuseSampler"], "");
+
+        if (this._scene) {
+            this._effectFog = this._scene.getEngine().createEffect("sprites",
+                [VertexBuffer.PositionKind, "options", "offsets", "inverts", "cellInfo", VertexBuffer.ColorKind],
+                ["view", "projection", "textureInfos", "alphaTest", "vFogInfos", "vFogColor"],
+                ["diffuseSampler"], "#define FOG");
+        }
+    }
+
+    /**
+     * Render all child sprites
+     * @param sprites defines the list of sprites to render
+     * @param deltaTime defines the time since last frame
+     * @param viewMatrix defines the viewMatrix to use to render the sprites
+     * @param projectionMatrix defines the projectionMatrix to use to render the sprites
+     * @param customSpriteUpdate defines a custom function to update the sprites data before they render
+     */
+    public render(sprites: ThinSprite[], deltaTime: number, viewMatrix: IMatrixLike, projectionMatrix: IMatrixLike, customSpriteUpdate: Nullable<(sprite: ThinSprite, baseSize: ISize) => void> = null): void {
+        if (!this.texture || !this.texture.isReady() || !sprites.length) {
+            return;
+        }
+
+        let effect = this._effectBase;
+        let shouldRenderFog = false;
+        if (this.fogEnabled && this._scene && this._scene.fogEnabled && this._scene.fogMode !== 0) {
+            effect = this._effectFog;
+            shouldRenderFog = true;
+        }
+
+        // Check
+        if (!effect.isReady()) {
+            return;
+        }
+
+        const engine = this._engine;
+        const useRightHandedSystem = !!(this._scene && this._scene.useRightHandedSystem);
+        const baseSize = this.texture.getBaseSize();
+
+        // Sprites
+        const max = Math.min(this._capacity, sprites.length);
+
+        let offset = 0;
+        let noSprite = true;
+        for (var index = 0; index < max; index++) {
+            const sprite = sprites[index];
+            if (!sprite || !sprite.isVisible) {
+                continue;
+            }
+
+            noSprite = false;
+            sprite._animate(deltaTime);
+
+            this._appendSpriteVertex(offset++, sprite, 0, 0, baseSize, useRightHandedSystem, customSpriteUpdate);
+            if (!this._useInstancing) {
+                this._appendSpriteVertex(offset++, sprite, 1, 0, baseSize, useRightHandedSystem, customSpriteUpdate);
+                this._appendSpriteVertex(offset++, sprite, 1, 1, baseSize, useRightHandedSystem, customSpriteUpdate);
+                this._appendSpriteVertex(offset++, sprite, 0, 1, baseSize, useRightHandedSystem, customSpriteUpdate);
+            }
+        }
+
+        if (noSprite) {
+            return;
+        }
+
+        this._buffer.update(this._vertexData);
+
+        const culling = engine.depthCullingState.cull || true;
+        const zOffset = engine.depthCullingState.zOffset;
+
+        // Handle Right Handed
+        if (useRightHandedSystem) {
+            this._scene!.getEngine().setState(culling, zOffset, false, false);
+        }
+
+        // Render
+        engine.enableEffect(effect);
+
+        effect.setTexture("diffuseSampler", this.texture);
+        effect.setMatrix("view", viewMatrix);
+        effect.setMatrix("projection", projectionMatrix);
+
+        // Scene Info
+        if (shouldRenderFog) {
+            const scene = this._scene!;
+
+            // Fog
+            effect.setFloat4("vFogInfos", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
+            effect.setColor3("vFogColor", scene.fogColor);
+        }
+
+        if (this._useVAO) {
+            if (!this._vertexArrayObject) {
+                this._vertexArrayObject = engine.recordVertexArrayObject(this._vertexBuffers, this._indexBuffer, effect);
+            }
+            engine.bindVertexArrayObject(this._vertexArrayObject, this._indexBuffer);
+        }
+        else {
+            // VBOs
+            engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
+        }
+
+        // Draw order
+        engine.depthCullingState.depthFunc = Constants.LEQUAL;
+        if (!this.disableDepthWrite) {
+            effect.setBool("alphaTest", true);
+            engine.setColorWrite(false);
+            if (this._useInstancing) {
+                engine.drawArraysType(Constants.MATERIAL_TriangleFanDrawMode, 0, 4, offset);
+            } else {
+                engine.drawElementsType(Constants.MATERIAL_TriangleFillMode, 0, (offset / 4) * 6);
+            }
+            engine.setColorWrite(true);
+            effect.setBool("alphaTest", false);
+        }
+
+        engine.setAlphaMode(this.blendMode);
+        if (this._useInstancing) {
+            engine.drawArraysType(Constants.MATERIAL_TriangleFanDrawMode, 0, 4, offset);
+        } else {
+            engine.drawElementsType(Constants.MATERIAL_TriangleFillMode, 0, (offset / 4) * 6);
+        }
+
+        if (this.autoResetAlpha) {
+            engine.setAlphaMode(Constants.ALPHA_DISABLE);
+        }
+
+        // Restore Right Handed
+        if (useRightHandedSystem) {
+            this._scene!.getEngine().setState(culling, zOffset, false, true);
+        }
+
+        engine.unbindInstanceAttributes();
+    }
+
+    private _appendSpriteVertex(index: number, sprite: ThinSprite, offsetX: number, offsetY: number, baseSize: ISize, useRightHandedSystem: boolean, customSpriteUpdate: Nullable<(sprite: ThinSprite, baseSize: ISize) => void>): void {
+        var arrayOffset = index * this._vertexBufferSize;
+
+        if (offsetX === 0) {
+            offsetX = this._epsilon;
+        }
+        else if (offsetX === 1) {
+            offsetX = 1 - this._epsilon;
+        }
+
+        if (offsetY === 0) {
+            offsetY = this._epsilon;
+        }
+        else if (offsetY === 1) {
+            offsetY = 1 - this._epsilon;
+        }
+
+        if (customSpriteUpdate) {
+            customSpriteUpdate(sprite, baseSize);
+        }
+        else {
+            const rowSize = baseSize.width / this.cellWidth;
+            const offset = (sprite.cellIndex / rowSize) >> 0;
+            sprite._xOffset = (sprite.cellIndex - offset * rowSize) * this.cellWidth / baseSize.width;
+            sprite._yOffset = offset * this.cellHeight / baseSize.height;
+            sprite._xSize = this.cellWidth;
+            sprite._ySize = this.cellHeight;
+        }
+
+        // Positions
+        this._vertexData[arrayOffset] = sprite.position.x;
+        this._vertexData[arrayOffset + 1] = sprite.position.y;
+        this._vertexData[arrayOffset + 2] = sprite.position.z;
+        this._vertexData[arrayOffset + 3] = sprite.angle;
+        // Options
+        this._vertexData[arrayOffset + 4] = sprite.width;
+        this._vertexData[arrayOffset + 5] = sprite.height;
+
+        if (!this._useInstancing) {
+            this._vertexData[arrayOffset + 6] = offsetX;
+            this._vertexData[arrayOffset + 7] = offsetY;
+        } else {
+            arrayOffset -= 2;
+        }
+
+        // Inverts according to Right Handed
+        if (useRightHandedSystem) {
+            this._vertexData[arrayOffset + 8] = sprite.invertU ? 0 : 1;
+        }
+        else {
+            this._vertexData[arrayOffset + 8] = sprite.invertU ? 1 : 0;
+        }
+
+        this._vertexData[arrayOffset + 9] = sprite.invertV ? 1 : 0;
+
+        this._vertexData[arrayOffset + 10] = sprite._xOffset;
+        this._vertexData[arrayOffset + 11] = sprite._yOffset;
+        this._vertexData[arrayOffset + 12] = sprite._xSize / baseSize.width;
+        this._vertexData[arrayOffset + 13] = sprite._ySize / baseSize.height;
+
+        // Color
+        this._vertexData[arrayOffset + 14] = sprite.color.r;
+        this._vertexData[arrayOffset + 15] = sprite.color.g;
+        this._vertexData[arrayOffset + 16] = sprite.color.b;
+        this._vertexData[arrayOffset + 17] = sprite.color.a;
+    }
+
+    /**
+     * Release associated resources
+     */
+    public dispose(): void {
+        if (this._buffer) {
+            this._buffer.dispose();
+            (<any>this._buffer) = null;
+        }
+
+        if (this._spriteBuffer) {
+            this._spriteBuffer.dispose();
+            (<any>this._spriteBuffer) = null;
+        }
+
+        if (this._indexBuffer) {
+            this._engine._releaseBuffer(this._indexBuffer);
+            (<any>this._indexBuffer) = null;
+        }
+
+        if (this._vertexArrayObject) {
+            this._engine.releaseVertexArrayObject(this._vertexArrayObject);
+            (<any>this._vertexArrayObject) = null;
+        }
+
+        if (this.texture) {
+            this.texture.dispose();
+            (<any>this.texture) = null;
+        }
+    }
+}

+ 140 - 0
src/Sprites/thinSprite.ts

@@ -0,0 +1,140 @@
+import { IVector3Like, IColor4Like } from "../Maths/math.like";
+import { Nullable } from "../types";
+
+/**
+ * ThinSprite Class used to represent a thin sprite
+ * This is the base class for sprites but can also directly be used with ThinEngine
+ * @see https://doc.babylonjs.com/babylon101/sprites
+ */
+export class ThinSprite {
+    /** Gets or sets the cell index in the sprite sheet */
+    public cellIndex: number = 0;
+    /** Gets or sets the cell reference in the sprite sheet, uses sprite's filename when added to sprite sheet */
+    public cellRef: string;
+    /** Gets or sets the current world position */
+    public position: IVector3Like;
+    /** Gets or sets the main color */
+    public color: IColor4Like;
+    /** Gets or sets the width */
+    public width = 1.0;
+    /** Gets or sets the height */
+    public height = 1.0;
+    /** Gets or sets rotation angle */
+    public angle = 0;
+    /** Gets or sets a boolean indicating if UV coordinates should be inverted in U axis */
+    public invertU = false;
+    /** Gets or sets a boolean indicating if UV coordinates should be inverted in B axis */
+    public invertV = false;
+    /** Gets or sets a boolean indicating if the sprite is visible (renderable). Default is true */
+    public isVisible = true;
+
+    /**
+     * Returns a boolean indicating if the animation is started
+     */
+    public get animationStarted() {
+        return this._animationStarted;
+    }
+
+    /** Gets the initial key for the animation (setting it will restart the animation)  */
+    public get fromIndex() {
+        return this._fromIndex;
+    }
+
+    /** Gets or sets the end key for the animation (setting it will restart the animation)  */
+    public get toIndex() {
+        return this._toIndex;
+    }
+
+    /** Gets or sets a boolean indicating if the animation is looping (setting it will restart the animation)  */
+    public get loopAnimation() {
+        return this._loopAnimation;
+    }
+
+    /** Gets or sets the delay between cell changes (setting it will restart the animation)  */
+    public get delay() {
+        return Math.max(this._delay, 1);
+    }
+
+    /** @hidden */
+    public _xOffset: number;
+    /** @hidden */
+    public _yOffset: number;
+    /** @hidden */
+    public _xSize: number;
+    /** @hidden */
+    public _ySize: number;
+
+    private _animationStarted = false;
+    private _loopAnimation = false;
+    private _fromIndex = 0;
+    private _toIndex = 0;
+    private _delay = 0;
+    private _direction = 1;
+    private _time = 0;
+    private _onBaseAnimationEnd: Nullable<() => void> = null;
+
+    /**
+     * Creates a new Thin Sprite
+     */
+    constructor() {
+        this.position = { x: 1.0, y: 1.0, z: 1.0 };
+        this.color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+    }
+
+    /**
+     * Starts an animation
+     * @param from defines the initial key
+     * @param to defines the end key
+     * @param loop defines if the animation must loop
+     * @param delay defines the start delay (in ms)
+     * @param onAnimationEnd defines a callback for when the animation ends
+     */
+    public playAnimation(from: number, to: number, loop: boolean, delay: number, onAnimationEnd: Nullable<() => void>): void {
+        this._fromIndex = from;
+        this._toIndex = to;
+        this._loopAnimation = loop;
+        this._delay = delay || 1;
+        this._animationStarted = true;
+        this._onBaseAnimationEnd = onAnimationEnd;
+
+        if (from < to) {
+            this._direction = 1;
+        } else {
+            this._direction = -1;
+            this._toIndex = from;
+            this._fromIndex = to;
+        }
+
+        this.cellIndex = from;
+        this._time = 0;
+    }
+
+    /** Stops current animation (if any) */
+    public stopAnimation(): void {
+        this._animationStarted = false;
+    }
+
+    /** @hidden */
+    public _animate(deltaTime: number): void {
+        if (!this._animationStarted) {
+            return;
+        }
+
+        this._time += deltaTime;
+        if (this._time > this._delay) {
+            this._time = this._time % this._delay;
+            this.cellIndex += this._direction;
+            if (this._direction > 0 && this.cellIndex > this._toIndex || this._direction < 0 && this.cellIndex < this._fromIndex) {
+                if (this._loopAnimation) {
+                    this.cellIndex = this._direction > 0 ? this._fromIndex : this._toIndex;
+                } else {
+                    this.cellIndex = this._toIndex;
+                    this._animationStarted = false;
+                    if (this._onBaseAnimationEnd) {
+                        this._onBaseAnimationEnd();
+                    }
+                }
+            }
+        }
+    }
+}

+ 0 - 6
src/assetContainer.ts

@@ -239,9 +239,6 @@ export class AssetContainer extends AbstractScene {
         this.lights.forEach((o) => {
             this.scene.addLight(o);
         });
-        this.geometries.forEach((o) => {
-            this.scene.addGeometry(o);
-        });
         this.meshes.forEach((o) => {
             this.scene.addMesh(o);
         });
@@ -300,9 +297,6 @@ export class AssetContainer extends AbstractScene {
         this.lights.forEach((o) => {
             this.scene.removeLight(o);
         });
-        this.geometries.forEach((o) => {
-            this.scene.removeGeometry(o);
-        });
         this.meshes.forEach((o) => {
             this.scene.removeMesh(o);
         });

BIN
tests/validation/ReferenceImages/ktx2decoder.png


BIN
tests/validation/ReferenceImages/node-material-pbr-1.png


+ 1 - 1
tests/validation/config.json

@@ -79,7 +79,7 @@
         },    
         {
             "title": "Node material PBR 1",
-            "playgroundId": "#D8AK3Z#15",
+            "playgroundId": "#D8AK3Z#17",
             "referenceImage": "node-material-pbr-1.png"
         },    
         {