Explorar el Código

Merge pull request #9059 from BabylonJS/master

Nightly
mergify[bot] hace 4 años
padre
commit
817716f3df
Se han modificado 83 ficheros con 8106 adiciones y 5259 borrados
  1. 5 0
      dist/preview release/babylon.d.ts
  2. 1 1
      dist/preview release/babylon.js
  3. 20 0
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 10 0
      dist/preview release/babylon.module.d.ts
  6. 30 21
      dist/preview release/documentation.d.ts
  7. 48 48
      dist/preview release/gui/babylon.gui.js
  8. 1 1
      dist/preview release/gui/babylon.gui.js.map
  9. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  10. 2238 1404
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  11. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  12. 407 114
      dist/preview release/inspector/babylon.inspector.d.ts
  13. 845 257
      dist/preview release/inspector/babylon.inspector.module.d.ts
  14. 6 6
      dist/preview release/materialsLibrary/babylon.cellMaterial.js
  15. 1 1
      dist/preview release/materialsLibrary/babylon.cellMaterial.js.map
  16. 5 5
      dist/preview release/materialsLibrary/babylon.customMaterial.js
  17. 1 1
      dist/preview release/materialsLibrary/babylon.customMaterial.js.map
  18. 6 6
      dist/preview release/materialsLibrary/babylon.fireMaterial.js
  19. 1 1
      dist/preview release/materialsLibrary/babylon.fireMaterial.js.map
  20. 6 6
      dist/preview release/materialsLibrary/babylon.furMaterial.js
  21. 1 1
      dist/preview release/materialsLibrary/babylon.furMaterial.js.map
  22. 6 6
      dist/preview release/materialsLibrary/babylon.gradientMaterial.js
  23. 1 1
      dist/preview release/materialsLibrary/babylon.gradientMaterial.js.map
  24. 6 6
      dist/preview release/materialsLibrary/babylon.gridMaterial.js
  25. 1 1
      dist/preview release/materialsLibrary/babylon.gridMaterial.js.map
  26. 6 6
      dist/preview release/materialsLibrary/babylon.lavaMaterial.js
  27. 1 1
      dist/preview release/materialsLibrary/babylon.lavaMaterial.js.map
  28. 6 6
      dist/preview release/materialsLibrary/babylon.mixMaterial.js
  29. 1 1
      dist/preview release/materialsLibrary/babylon.mixMaterial.js.map
  30. 6 6
      dist/preview release/materialsLibrary/babylon.normalMaterial.js
  31. 1 1
      dist/preview release/materialsLibrary/babylon.normalMaterial.js.map
  32. 6 6
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.js
  33. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.js.map
  34. 6 6
      dist/preview release/materialsLibrary/babylon.simpleMaterial.js
  35. 1 1
      dist/preview release/materialsLibrary/babylon.simpleMaterial.js.map
  36. 6 6
      dist/preview release/materialsLibrary/babylon.skyMaterial.js
  37. 1 1
      dist/preview release/materialsLibrary/babylon.skyMaterial.js.map
  38. 6 6
      dist/preview release/materialsLibrary/babylon.terrainMaterial.js
  39. 1 1
      dist/preview release/materialsLibrary/babylon.terrainMaterial.js.map
  40. 7 7
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js
  41. 1 1
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js.map
  42. 1 1
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.min.js
  43. 38 29
      dist/preview release/materialsLibrary/babylon.waterMaterial.js
  44. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.js.map
  45. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  46. 25 21
      dist/preview release/materialsLibrary/babylonjs.materials.d.ts
  47. 80 71
      dist/preview release/materialsLibrary/babylonjs.materials.js
  48. 1 1
      dist/preview release/materialsLibrary/babylonjs.materials.js.map
  49. 1 1
      dist/preview release/materialsLibrary/babylonjs.materials.min.js
  50. 50 42
      dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts
  51. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  52. 3 0
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  53. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  54. 1 1
      dist/preview release/packagesSizeBaseLine.json
  55. 10 0
      dist/preview release/viewer/babylon.module.d.ts
  56. 2 2
      dist/preview release/viewer/babylon.viewer.js
  57. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  58. 3 0
      dist/preview release/what's new.md
  59. 38 33
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx
  60. 85 50
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx
  61. 1140 363
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx
  62. 43 16
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx
  63. 94 18
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx
  64. 211 316
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx
  65. 78 35
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx
  66. 1204 1154
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss
  67. 294 272
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx
  68. 174 124
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx
  69. 93 86
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx
  70. 104 114
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx
  71. 22 25
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx
  72. 3 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/playhead.tsx
  73. 155 163
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx
  74. 37 36
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx
  75. 114 98
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx
  76. 80 131
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx
  77. 148 74
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx
  78. 1 1
      materialsLibrary/src/triPlanar/triplanar.vertex.fx
  79. 5 2
      materialsLibrary/src/water/water.vertex.fx
  80. 30 22
      materialsLibrary/src/water/waterMaterial.ts
  81. 3 0
      nodeEditor/src/diagram/graphCanvas.tsx
  82. 13 0
      src/Lights/Shadows/shadowGenerator.ts
  83. 6 0
      src/Materials/materialHelper.ts

+ 5 - 0
dist/preview release/babylon.d.ts

@@ -17964,6 +17964,11 @@ declare module BABYLON {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/babylon.js


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

@@ -59560,6 +59560,21 @@ var ShadowGenerator = /** @class */ (function () {
     ShadowGenerator.prototype.getLight = function () {
         return this._light;
     };
+    Object.defineProperty(ShadowGenerator.prototype, "mapSize", {
+        /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get: function () {
+            return this._mapSize;
+        },
+        set: function (size) {
+            this._mapSize = size;
+            this._light._markMeshesAsLightDirty();
+            this.recreateShadowMap();
+        },
+        enumerable: false,
+        configurable: true
+    });
     ShadowGenerator.prototype._initializeGenerator = function () {
         this._light._markMeshesAsLightDirty();
         this._initializeShadowMap();
@@ -102933,6 +102948,7 @@ var MaterialHelper = /** @class */ (function () {
         defines["SHADOWPCSS" + lightIndex] = false;
         defines["SHADOWPOISSON" + lightIndex] = false;
         defines["SHADOWESM" + lightIndex] = false;
+        defines["SHADOWCLOSEESM" + lightIndex] = false;
         defines["SHADOWCUBE" + lightIndex] = false;
         defines["SHADOWLOWQUALITY" + lightIndex] = false;
         defines["SHADOWMEDIUMQUALITY" + lightIndex] = false;
@@ -103013,6 +103029,7 @@ var MaterialHelper = /** @class */ (function () {
                 defines["SHADOWPCSS" + index] = false;
                 defines["SHADOWPOISSON" + index] = false;
                 defines["SHADOWESM" + index] = false;
+                defines["SHADOWCLOSEESM" + index] = false;
                 defines["SHADOWCUBE" + index] = false;
                 defines["SHADOWLOWQUALITY" + index] = false;
                 defines["SHADOWMEDIUMQUALITY" + index] = false;
@@ -103124,6 +103141,9 @@ var MaterialHelper = /** @class */ (function () {
                 if (defines["SHADOWESM" + lightIndex]) {
                     fallbacks.addFallback(rank, "SHADOWESM" + lightIndex);
                 }
+                if (defines["SHADOWCLOSEESM" + lightIndex]) {
+                    fallbacks.addFallback(rank, "SHADOWCLOSEESM" + lightIndex);
+                }
             }
         }
         return lightFallbackRank++;

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/babylon.max.js.map


+ 10 - 0
dist/preview release/babylon.module.d.ts

@@ -18490,6 +18490,11 @@ declare module "babylonjs/Lights/Shadows/shadowGenerator" {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.
@@ -98590,6 +98595,11 @@ declare module BABYLON {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.

+ 30 - 21
dist/preview release/documentation.d.ts

@@ -17964,6 +17964,11 @@ declare module BABYLON {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.
@@ -87119,61 +87124,65 @@ declare module BABYLON {
         private _maxSimultaneousLights;
         maxSimultaneousLights: number;
         /**
-        * @param {number}: Represents the wind force
-        */
+         * Defines the wind force.
+         */
         windForce: number;
         /**
-        * @param {Vector2}: The direction of the wind in the plane (X, Z)
-        */
+         * Defines the direction of the wind in the plane (X, Z).
+         */
         windDirection: BABYLON.Vector2;
         /**
-        * @param {number}: Wave height, represents the height of the waves
-        */
+         * Defines the height of the waves.
+         */
         waveHeight: number;
         /**
-        * @param {number}: Bump height, represents the bump height related to the bump map
-        */
+         * Defines the bump height related to the bump map.
+         */
         bumpHeight: number;
         /**
-         * @param {boolean}: Add a smaller moving bump to less steady waves.
+         * Defines wether or not: to add a smaller moving bump to less steady waves.
          */
         private _bumpSuperimpose;
         bumpSuperimpose: boolean;
         /**
-         * @param {boolean}: Color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
+         * Defines wether or not color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
          */
         private _fresnelSeparate;
         fresnelSeparate: boolean;
         /**
-         * @param {boolean}: bump Waves modify the reflection.
+         * Defines wether or not bump Wwves modify the reflection.
          */
         private _bumpAffectsReflection;
         bumpAffectsReflection: boolean;
         /**
-        * @param {number}: The water color blended with the refraction (near)
-        */
+         * Defines the water color blended with the refraction (near).
+         */
         waterColor: BABYLON.Color3;
         /**
-        * @param {number}: The blend factor related to the water color
-        */
+         * Defines the blend factor related to the water color.
+         */
         colorBlendFactor: number;
         /**
-         * @param {number}: The water color blended with the reflection (far)
+         * Defines the water color blended with the reflection (far).
          */
         waterColor2: BABYLON.Color3;
         /**
-         * @param {number}: The blend factor related to the water color (reflection, far)
+         * Defines the blend factor related to the water color (reflection, far).
          */
         colorBlendFactor2: number;
         /**
-        * @param {number}: Represents the maximum length of a wave
-        */
+         * Defines the maximum length of a wave.
+         */
         waveLength: number;
         /**
-        * @param {number}: Defines the waves speed
-        */
+         * Defines the waves speed.
+         */
         waveSpeed: number;
         /**
+         * Defines the number of times waves are repeated. This is typically used to adjust waves count according to the ground's size where the material is applied on.
+         */
+        waveCount: number;
+        /**
          * Sets or gets whether or not automatic clipping should be enabled or not. Setting to true will save performances and
          * will avoid calculating useless pixels in the pixel shader of the water material.
          */

+ 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");
@@ -13015,7 +13015,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");
@@ -13404,7 +13404,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__);
 
 
@@ -13629,7 +13629,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 = [
@@ -13794,7 +13794,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");
 
@@ -13937,7 +13937,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");
 
@@ -14243,7 +14243,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__);
 
 /**
@@ -14562,7 +14562,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");
 
@@ -14605,7 +14605,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");
@@ -14786,7 +14786,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");
 
@@ -14943,7 +14943,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");
 
@@ -15349,7 +15349,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");
@@ -15435,7 +15435,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");
@@ -15929,7 +15929,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");
@@ -15984,7 +15984,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");
@@ -16111,7 +16111,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");
@@ -16197,7 +16197,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");
 
@@ -16322,7 +16322,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");
 
@@ -16513,7 +16513,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");
 
@@ -16780,7 +16780,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");
@@ -17096,7 +17096,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';
@@ -17118,7 +17118,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';
@@ -17141,7 +17141,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__);
 
 
@@ -17443,14 +17443,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__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 6 - 6
dist/preview release/inspector/babylon.inspector.bundle.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2238 - 1404
dist/preview release/inspector/babylon.inspector.bundle.max.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 407 - 114
dist/preview release/inspector/babylon.inspector.d.ts

@@ -518,10 +518,21 @@ declare module INSPECTOR {
         index: string;
         selected: boolean;
         selectControlPoint: (id: string) => void;
+        framesInCanvasView: {
+            from: number;
+            to: number;
+        };
     }
-    export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps> {
+    /**
+     * Renders the control point to a keyframe.
+     */
+    export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, {
+        visiblePoint: BABYLON.Vector2;
+    }> {
         constructor(props: IAnchorSvgPointProps);
-        select(): void;
+        componentDidUpdate(prevProps: IAnchorSvgPointProps, prevState: any): void;
+        select: () => void;
+        setVisiblePoint(): BABYLON.Vector2;
         render(): JSX.Element;
     }
 }
@@ -551,10 +562,19 @@ declare module INSPECTOR {
         selectedControlPoint: (type: string, id: string) => void;
         isLeftActive: boolean;
         isRightActive: boolean;
+        framesInCanvasView: {
+            from: number;
+            to: number;
+        };
     }
+    /**
+     * Renders the Keyframe as an SVG Element for the Canvas component.
+     * Holds the two control points to generate the proper curve.
+     */
     export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         constructor(props: IKeyframeSvgPointProps);
-        select(e: React.MouseEvent<SVGImageElement>): void;
+        select: (e: React.MouseEvent<SVGImageElement>) => void;
+        selectedControlPointId: (type: string) => void;
         render(): JSX.Element;
     }
 }
@@ -564,18 +584,26 @@ declare module INSPECTOR {
         updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
         scale: number;
         viewBoxScale: number;
-        selectKeyframe: (id: string, multiselect: boolean) => void;
-        selectedControlPoint: (type: string, id: string) => void;
         deselectKeyframes: () => void;
         removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
         panningY: (panningY: number) => void;
         panningX: (panningX: number) => void;
         setCurrentFrame: (direction: number) => void;
-        positionCanvas?: number;
+        positionCanvas?: BABYLON.Vector2;
         repositionCanvas?: boolean;
         canvasPositionEnded: () => void;
         resetActionableKeyframe: () => void;
+        framesInCanvasView: {
+            from: number;
+            to: number;
+        };
+        framesResized: number;
     }
+    /**
+     * The SvgDraggableArea is a wrapper for SVG Canvas the interaction
+     *
+     * Here we control the drag and key behavior for the SVG components.
+     */
     export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         panX: number;
         panY: number;
@@ -590,23 +618,24 @@ declare module INSPECTOR {
         private _playheadSelected;
         private _movedX;
         private _movedY;
+        private _isControlKeyPress;
         readonly _dragBuffer: number;
         readonly _draggingMultiplier: number;
         constructor(props: ISvgDraggableAreaProps);
         componentDidMount(): void;
-        componentWillReceiveProps(newProps: ISvgDraggableAreaProps): void;
-        dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-        dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-        drag(e: React.TouchEvent<SVGSVGElement>): void;
-        drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-        dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-        dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-        getMousePosition(e: React.TouchEvent<SVGSVGElement>): BABYLON.Vector2 | undefined;
-        getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>): BABYLON.Vector2 | undefined;
+        componentDidUpdate(prevProps: ISvgDraggableAreaProps): void;
+        dragStart: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        drag: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        dragEnd: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        getMousePosition: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => BABYLON.Vector2 | undefined;
+        /**
+        * Handles the canvas panning direction and sets the X and Y values to move the
+        * SVG canvas
+        */
         panDirection(): void;
         keyDown(e: KeyboardEvent): void;
         keyUp(e: KeyboardEvent): void;
-        focus(e: React.MouseEvent<SVGSVGElement>): void;
+        focus: (e: React.MouseEvent<SVGSVGElement>) => void;
         isNotControlPointActive(): boolean;
         render(): JSX.Element;
     }
@@ -629,24 +658,27 @@ declare module INSPECTOR {
         selected: BABYLON.IAnimationKey | null;
         currentFrame: number;
         onCurrentFrameChange: (frame: number) => void;
-        repositionCanvas: (frame: number) => void;
+        repositionCanvas: (keyframe: BABYLON.IAnimationKey) => void;
         playPause: (direction: number) => void;
         isPlaying: boolean;
         scrollable: React.RefObject<HTMLDivElement>;
     }
+    /**
+     * The playback controls for the animation editor
+     */
     export class Controls extends React.Component<IControlsProps, {
         selected: BABYLON.IAnimationKey;
         playingType: string;
     }> {
         readonly _sizeOfKeyframe: number;
         constructor(props: IControlsProps);
-        playBackwards(): void;
-        play(): void;
-        pause(): void;
-        moveToAnimationStart(): void;
-        moveToAnimationEnd(): void;
-        nextKeyframe(): void;
-        previousKeyframe(): void;
+        playBackwards: () => void;
+        play: () => void;
+        pause: () => void;
+        moveToAnimationStart: () => void;
+        moveToAnimationEnd: () => void;
+        nextKeyframe: () => void;
+        previousKeyframe: () => void;
         render(): JSX.Element;
     }
 }
@@ -662,8 +694,15 @@ declare module INSPECTOR {
         isPlaying: boolean;
         animationLimit: number;
         fps: number;
-        repositionCanvas: (frame: number) => void;
+        repositionCanvas: (keyframe: BABYLON.IAnimationKey) => void;
+        resizeWindowProportion: number;
     }
+    /**
+     * The Timeline for the curve editor
+     *
+     * Has a scrollbar that can be resized and move to left and right.
+     * The timeline does not affect the Canvas but only the frame container.
+     */
     export class Timeline extends React.Component<ITimelineProps, {
         selected: BABYLON.IAnimationKey;
         activeKeyframe: number | null;
@@ -684,35 +723,55 @@ declare module INSPECTOR {
         readonly _marginScrollbar: number;
         constructor(props: ITimelineProps);
         componentDidMount(): void;
+        componentDidUpdate(prevProps: ITimelineProps): void;
         componentWillUnmount(): void;
         isEnterKeyUp(event: KeyboardEvent): void;
         onInputBlur(event: React.FocusEvent<HTMLInputElement>): void;
         setControlState(): void;
+        /**
+        * @param {number} start Frame from which the scrollbar should begin.
+        * @param {number} end Last frame for the timeline.
+        */
         calculateScrollWidth(start: number, end: number): number | undefined;
         playBackwards(event: React.MouseEvent<HTMLDivElement>): void;
         play(event: React.MouseEvent<HTMLDivElement>): void;
         pause(event: React.MouseEvent<HTMLDivElement>): void;
-        setCurrentFrame(event: React.MouseEvent<HTMLDivElement>): void;
+        setCurrentFrame: (event: React.MouseEvent<HTMLDivElement>) => void;
+        /**
+        * Handles the change of number of frames available in the timeline.
+        */
         handleLimitChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-        dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-        drag(e: React.TouchEvent<SVGSVGElement>): void;
-        drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        dragStart: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        drag: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        /**
+        * Check if the frame is being used as a Keyframe by the animation
+        */
         isFrameBeingUsed(frame: number, direction: number): number | false;
-        dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-        dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-        scrollDragStart(e: React.TouchEvent<HTMLDivElement>): void;
-        scrollDragStart(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-        scrollDrag(e: React.TouchEvent<HTMLDivElement>): void;
-        scrollDrag(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-        scrollDragEnd(e: React.TouchEvent<HTMLDivElement>): void;
-        scrollDragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
+        dragEnd: (e: React.MouseEvent<SVGSVGElement, MouseEvent>) => void;
+        scrollDragStart: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
+        scrollDrag: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
+        scrollDragEnd: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
+        /**
+        * Sets the start, end and selection length of the scrollbar. This will control the width and
+        * height of the scrollbar as well as the number of frames available
+        * @param {number} pageX Controls the X axis of the scrollbar movement.
+        */
         moveScrollbar(pageX: number): void;
+        /**
+        * Controls the resizing of the scrollbar from the right handle
+        */
         resizeScrollbarRight(clientX: number): void;
+        /**
+        * Controls the resizing of the scrollbar from the left handle
+        */
         resizeScrollbarLeft(clientX: number): void;
+        /**
+        * Returns array with the expected length between two numbers
+        */
         range(start: number, end: number): number[];
         getKeyframe(frame: number): false | BABYLON.IAnimationKey | undefined;
         getCurrentFrame(frame: number): boolean;
+        dragDomFalse: () => boolean;
         render(): JSX.Element;
     }
 }
@@ -722,6 +781,9 @@ declare module INSPECTOR {
         open: boolean;
         close: () => void;
     }
+    /**
+     * Renders the notification for the user
+     */
     export class Notification extends React.Component<IPlayheadProps> {
         constructor(props: IPlayheadProps);
         render(): JSX.Element;
@@ -731,27 +793,49 @@ declare module INSPECTOR {
     interface IGraphActionsBarProps {
         addKeyframe: () => void;
         removeKeyframe: () => void;
+        frameSelectedKeyframes: () => void;
         handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         flatTangent: () => void;
         brokeTangents: () => void;
-        setLerpMode: () => void;
+        setLerpToActiveControlPoint: () => void;
         brokenMode: boolean;
         lerpMode: boolean;
         actionableKeyframe: IActionableKeyFrame;
         title: string;
-        close: (event: any) => void;
         enabled: boolean;
-        setKeyframeValue: () => void;
+        setKeyframeValue: (actionableKeyframe: IActionableKeyFrame) => void;
+        frameRange: {
+            min: number | undefined;
+            max: number | undefined;
+        };
     }
-    export class GraphActionsBar extends React.Component<IGraphActionsBarProps> {
+    /**
+     * Has the buttons and actions for the Canvas Graph.
+     * Handles input change and actions (flat, broken mode, set linear control points)
+     */
+    export class GraphActionsBar extends React.Component<IGraphActionsBarProps, {
+        frame: string;
+        value: string;
+        min: number | undefined;
+        max: number | undefined;
+    }> {
         private _frameInput;
         private _valueInput;
         constructor(props: IGraphActionsBarProps);
         componentDidMount(): void;
+        componentDidUpdate(prevProps: IGraphActionsBarProps, prevState: any): void;
+        selectedKeyframeChanged(keyframe: IActionableKeyFrame): {
+            frame: string;
+            value: string;
+        };
         componentWillUnmount(): void;
         isEnterKeyUp(event: KeyboardEvent): void;
-        onBlur(event: React.FocusEvent<HTMLInputElement>): void;
+        onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
+        getFrame(): string | number;
+        getValue(): string | number;
+        handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+        handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         render(): JSX.Element;
     }
 }
@@ -763,10 +847,13 @@ declare module INSPECTOR {
         onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
         setNotificationMessage: (message: string) => void;
         finishedUpdate: () => void;
-        addedNewAnimation: () => void;
+        addedNewAnimation: (animation: BABYLON.Animation) => void;
         fps: number;
         selectedToUpdate?: BABYLON.Animation | undefined;
     }
+    /**
+     * Controls the creation of a new animation
+     */
     export class AddAnimation extends React.Component<IAddAnimationProps, {
         animationName: string;
         animationTargetProperty: string;
@@ -784,17 +871,17 @@ declare module INSPECTOR {
             animationTargetProperty: string;
             isUpdating: boolean;
         };
-        componentWillReceiveProps(nextProps: IAddAnimationProps): void;
-        updateAnimation(): void;
+        componentDidUpdate(prevProps: IAddAnimationProps, prevState: any): void;
+        updateAnimation: () => void;
         getTypeAsString(type: number): "Float" | "Quaternion" | "Vector3" | "Vector2" | "Size" | "Color3" | "Color4";
-        addAnimation(): void;
+        addAnimation: () => void;
         raiseOnPropertyChanged(newValue: BABYLON.Animation[], previousValue: BABYLON.Animation[]): void;
         raiseOnPropertyUpdated(newValue: string | number | undefined, previousValue: string | number, property: string): void;
-        handleNameChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handlePathChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>): void;
-        handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>): void;
+        handlePathChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+        handleNameChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+        handleTypeChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
+        handlePropertyChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+        handleLoopModeChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
         render(): JSX.Element;
     }
 }
@@ -833,13 +920,19 @@ declare module INSPECTOR {
         color: string;
         coordinate: SelectedCoordinate;
     }
+    /**
+     * Renders a list of current animations.
+     */
     export class AnimationListTree extends React.Component<IAnimationListTreeProps, {
         selectedCoordinate: SelectedCoordinate;
         selectedAnimation: number;
         animationList: Item[] | null;
+        animations: BABYLON.Nullable<BABYLON.Animation[]> | BABYLON.Animation;
     }> {
         constructor(props: IAnimationListTreeProps);
-        deleteAnimation(): void;
+        componentDidUpdate(prevProps: IAnimationListTreeProps): void;
+        deleteAnimation: () => void;
+        raiseOnPropertyChanged(newValue: BABYLON.Animation[], previousValue: BABYLON.Animation[]): void;
         generateList(): Item[] | null;
         toggleProperty(index: number): void;
         setSelectedCoordinate(animation: BABYLON.Animation, coordinate: SelectedCoordinate, index: number): void;
@@ -876,14 +969,17 @@ declare module INSPECTOR {
         setNotificationMessage: (message: string) => void;
         animationsLoaded: (numberOfAnimations: number) => void;
     }
+    /**
+     * Loads animation locally or from the Babylon.js Snippet Server
+     */
     export class LoadSnippet extends React.Component<ILoadSnippetProps, {
         snippetId: string;
     }> {
         private _serverAddress;
         constructor(props: ILoadSnippetProps);
-        change(value: string): void;
-        loadFromFile(file: File): void;
-        loadFromSnippet(): void;
+        change: (value: string) => void;
+        loadFromFile: (file: File) => void;
+        loadFromSnippet: () => void;
         render(): JSX.Element;
     }
 }
@@ -906,14 +1002,17 @@ declare module INSPECTOR {
         index: number;
         selected: boolean;
     }
+    /**
+     * Saves the animation snippet to the Babylon.js site or downloads the animation file locally
+     */
     export class SaveSnippet extends React.Component<ISaveSnippetProps, {
         selectedAnimations: SelectedAnimation[];
     }> {
         constructor(props: ISaveSnippetProps);
-        handleCheckboxChange(e: React.ChangeEvent<HTMLInputElement>): void;
+        handleCheckboxChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         stringifySelectedAnimations(): string;
-        saveToFile(): void;
-        saveToSnippet(): void;
+        saveToFile: () => void;
+        saveToSnippet: () => void;
         render(): JSX.Element;
     }
 }
@@ -933,6 +1032,9 @@ declare module INSPECTOR {
         deselectAnimation: () => void;
         fps: number;
     }
+    /**
+     * Renders the Curve Editor controls to create, save, remove, load and edit animations
+     */
     export class EditorControls extends React.Component<IEditorControlsProps, {
         isAnimationTabOpen: boolean;
         isEditTabOpen: boolean;
@@ -945,16 +1047,31 @@ declare module INSPECTOR {
         selected: BABYLON.Animation | undefined;
     }> {
         constructor(props: IEditorControlsProps);
-        componentWillReceiveProps(newProps: IEditorControlsProps): void;
-        animationAdded(): void;
-        finishedUpdate(): void;
+        componentDidUpdate(prevProps: IEditorControlsProps): void;
+        onAnimationAdded: (animation: BABYLON.Animation) => void;
+        finishedUpdate: () => void;
         recountAnimations(): number;
-        changeLoopBehavior(): void;
+        changeLoopBehavior: () => void;
+        handleFirstTab: () => void;
+        handleSecondTab: () => void;
+        handleThirdTab: () => void;
+        handleFourthTab: () => void;
         handleTabs(tab: number): void;
-        handleChangeFps(fps: number): void;
-        emptiedList(): void;
-        animationsLoaded(numberOfAnimations: number): void;
-        editAnimation(selected: BABYLON.Animation): void;
+        handleChangeFps: (fps: number) => void;
+        /**
+         * Cleans the list when has been emptied
+         */
+        onEmptiedList: () => void;
+        /**
+         * When animations have been reloaded update tabs
+         */
+        animationsLoaded: (numberOfAnimations: number) => void;
+        editAnimation: (selected: BABYLON.Animation) => void;
+        setSnippetId: (id: string) => void;
+        /**
+        * Marks animation tab closed and hides the tab
+        */
+        onCloseAddAnimation: () => void;
         render(): JSX.Element;
     }
 }
@@ -963,17 +1080,20 @@ declare module INSPECTOR {
         current: CurveScale;
         action?: (event: CurveScale) => void;
     }
+    /**
+     * Displays the current scale
+     */
     export class ScaleLabel extends React.Component<ISwitchButtonProps, {
         current: CurveScale;
     }> {
         constructor(props: ISwitchButtonProps);
         renderLabel(scale: CurveScale): "" | "DEG" | "FLT" | "INT" | "RAD";
+        onClickHandle: () => void;
         render(): JSX.Element;
     }
 }
 declare module INSPECTOR {
     interface IAnimationCurveEditorComponentProps {
-        close: (event: any) => void;
         playOrPause?: () => void;
         scene: BABYLON.Scene;
         entity: BABYLON.IAnimatable | BABYLON.TargetedAnimation;
@@ -1002,6 +1122,9 @@ declare module INSPECTOR {
         color: string;
         id: string;
     }
+    /**
+     * BABYLON.Animation curve Editor Component
+     */
     export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
         isOpen: boolean;
         selected: BABYLON.Animation | null;
@@ -1029,56 +1152,81 @@ declare module INSPECTOR {
         panningX: number;
         repositionCanvas: boolean;
         actionableKeyframe: IActionableKeyFrame;
-        valueScale: CurveScale;
+        valueScaleType: CurveScale;
+        valueScale: number;
         canvasLength: number;
+        lastKeyframeCreated: BABYLON.Nullable<string>;
+        canvasWidthScale: number;
+        valuesPositionResize: number;
+        framesInCanvasView: {
+            from: number;
+            to: number;
+        };
+        maxFrame: number | undefined;
+        minFrame: number | undefined;
+        framesResized: number;
     }> {
+        readonly _entityName: string;
         private _snippetUrl;
         private _heightScale;
         private _scaleFactor;
         private _currentScale;
-        readonly _entityName: string;
+        private _pixelFrameUnit;
         private _svgKeyframes;
         private _isPlaying;
         private _graphCanvas;
         private _editor;
+        private _editorWindow;
+        private _resizeId;
         private _svgCanvas;
         private _isTargetedAnimation;
-        private _pixelFrameUnit;
+        private _resizedTimeline;
         private _onBeforeRenderObserver;
         private _mainAnimatable;
         constructor(props: IAnimationCurveEditorComponentProps);
         componentDidMount(): void;
         componentDidUpdate(prevProps: IAnimationCurveEditorComponentProps, prevState: any): void;
+        componentWillUnmount(): void;
         onCurrentFrameChangeChangeScene(value: number): void;
         /**
          * Notifications
          * To add notification we set the state and clear to make the notification bar hide.
          */
-        clearNotification(): void;
+        clearNotification: () => void;
         /**
          * Zoom and Scroll
          * This section handles zoom and scroll
          * of the graph area.
          */
-        zoom(e: React.WheelEvent<HTMLDivElement>): void;
+        zoom: (e: React.WheelEvent<HTMLDivElement>) => void;
+        /**
+         * Returns Array with labels and values for Frame axis in Canvas
+         */
         setFrameAxis(currentLength: number): {
             value: number;
             label: number;
         }[];
-        setValueLines(type: CurveScale): ({
+        /**
+         * Returns Array with labels, lines and values for Value axis in Canvas
+        */
+        setValueLines(): {
             value: number;
             label: string;
-        } | {
-            value: number;
-            label: number;
-        })[];
-        getValueLabel(i: number): number;
-        resetPlayheadOffset(): void;
-        encodeCurveId(animationName: string, coordinate: number): string;
+        }[];
+        /**
+         * Creates a string id from animation name and the keyframe index
+        */
+        encodeCurveId(animationName: string, keyframeIndex: number): string;
+        /**
+         * Returns the animation keyframe index and the animation selected coordinate (x, y, z)
+        */
         decodeCurveId(id: string): {
             order: number;
             coordinate: number;
         };
+        /**
+         * Returns the value from a keyframe
+        */
         getKeyframeValueFromAnimation(id: string): {
             frame: number;
             value: number;
@@ -1087,24 +1235,101 @@ declare module INSPECTOR {
          * Keyframe Manipulation
          * This section handles events from SvgDraggableArea.
          */
-        selectKeyframe(id: string, multiselect: boolean): void;
-        resetActionableKeyframe(): void;
-        selectedControlPoint(type: string, id: string): void;
-        deselectKeyframes(): void;
+        selectKeyframe: (id: string, multiselect: boolean) => void;
+        /**
+         * Determine if two control points are collinear (flat tangent)
+        */
+        hasCollinearPoints: (kf: IKeyframeSvgPoint | undefined) => boolean;
+        /**
+         * Returns the previous and next keyframe from a selected frame.
+        */
+        getPreviousAndNextKeyframe: (frame: number) => {
+            prev: number | undefined;
+            next: number | undefined;
+        };
+        /**
+         * Selects a keyframe in animation based on its Id
+        */
+        selectKeyframeFromId: (id: string, actionableKeyframe: IActionableKeyFrame) => void;
+        /**
+         * Resets the current selected keyframe as an updatable pairs by Graph BABYLON.GUI.Control Bar
+        */
+        resetActionableKeyframe: () => void;
+        /**
+         * Sets the selected control point.
+        */
+        selectedControlPoint: (type: string, id: string) => void;
+        /**
+         * Sets the selected control point.
+        */
+        deselectKeyframes: () => void;
+        /**
+         * Update the BABYLON.Animation Key values based on its type
+        */
         updateValuePerCoordinate(dataType: number, value: number | BABYLON.Vector2 | BABYLON.Vector3 | BABYLON.Color3 | BABYLON.Color4 | BABYLON.Size | BABYLON.Quaternion, newValue: number, coordinate?: number): number | BABYLON.Vector3 | BABYLON.Quaternion | BABYLON.Color3 | BABYLON.Color4 | BABYLON.Vector2 | BABYLON.Size;
-        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, id: string): void;
+        /**
+         * BABYLON.Animation should always have a keyframe at Frame Zero
+        */
+        forceFrameZeroToExist(keys: BABYLON.IAnimationKey[]): void;
+        /**
+         * Renders SVG points with dragging of the curve
+        */
+        renderPoints: (updatedSvgKeyFrame: IKeyframeSvgPoint, id: string) => void;
+        /**
+         * Updates the left control point on render points
+        */
         updateLeftControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: BABYLON.IAnimationKey, dataType: number, coordinate: number): void;
+        /**
+         * Updates the right control point on render points
+        */
         updateRightControlPoint(updatedSvgKeyFrame: IKeyframeSvgPoint, key: BABYLON.IAnimationKey, dataType: number, coordinate: number): void;
-        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        setKeyframeValue(): void;
-        setFlatTangent(): void;
-        setTangentMode(): void;
-        setBrokenMode(): void;
-        setLerpMode(): void;
-        addKeyframeClick(): void;
-        removeKeyframeClick(): void;
-        removeKeyframes(points: IKeyframeSvgPoint[]): void;
+        /**
+         * Get the current BABYLON.GUI.Control Point weight (how far the X value is multiplied)
+        */
+        getControlPointWeight(updatedSvgKeyFrame: IKeyframeSvgPoint): number;
+        /**
+         * Handles a Frame selection change
+        */
+        handleFrameChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+        /**
+         * Handles how a value change on a selected frame
+        */
+        handleValueChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
+        /**
+         * Set the Keyframe from input control in Graph BABYLON.GUI.Control Bar
+        */
+        setKeyframeValueFromInput: (actionableKeyframe: IActionableKeyFrame) => void;
+        /**
+         * Sets the SVG Keyframe value
+        */
+        setKeyframeValue: () => void;
+        /**
+         * Set the flat tangent to the current selected control points.
+        */
+        setFlatTangent: () => void;
+        /**
+         * Sets Broken mode of lines
+        */
+        setBrokenMode: () => void;
+        /**
+         * Sets a control point to be a linear interpolation with its Keyframe
+        */
+        setLerpToActiveControlPoint: () => void;
+        /**
+         * Adds a new keyframe to the curve on canvas click
+        */
+        addKeyframeClick: () => void;
+        /**
+         * Remove keyframe on click
+        */
+        removeKeyframeClick: () => void;
+        /**
+         * Remove the selected keyframes
+        */
+        removeKeyframes: (points: IKeyframeSvgPoint[]) => void;
+        /**
+         * Adds a keyframe
+        */
         addKeyFrame(event: React.MouseEvent<SVGSVGElement>): void;
         /**
          * Curve Rendering Functions
@@ -1112,9 +1337,21 @@ declare module INSPECTOR {
          */
         setKeyframePointLinear(point: BABYLON.Vector2, index: number): void;
         flatTangents(keyframes: BABYLON.IAnimationKey[], dataType: number): BABYLON.IAnimationKey[];
+        /**
+         * Return a Keyframe zero value depending on Type
+        */
         returnZero(dataType: number): 0 | BABYLON.Vector3 | BABYLON.Quaternion | BABYLON.Color3 | BABYLON.Color4 | BABYLON.Vector2 | BABYLON.Size;
+        /**
+         * Return the keyframe value as an array depending on type
+        */
         getValueAsArray(valueType: number, value: number | BABYLON.Vector2 | BABYLON.Vector3 | BABYLON.Color3 | BABYLON.Color4 | BABYLON.Size | BABYLON.Quaternion): number[];
+        /**
+         * Sets the keyframe value as an array depending on type
+        */
         setValueAsType(valueType: number, arrayValue: number[]): number | BABYLON.Vector3 | BABYLON.Quaternion | BABYLON.Color3 | BABYLON.Color4 | BABYLON.Vector2 | BABYLON.Size;
+        /**
+         * Returns the SVG Path Data to render the curve
+        */
         getPathData(animation: BABYLON.Animation | null): ICurveData[] | undefined;
         getAnimationData(animation: BABYLON.Animation): {
             loopMode: number | undefined;
@@ -1129,36 +1366,89 @@ declare module INSPECTOR {
             easingMode: number | undefined;
             valueType: number;
         };
+        calculateLinearTangents(keyframes: BABYLON.IAnimationKey[]): BABYLON.IAnimationKey[];
+        /**
+         * Calculates the proper linear tangents if there is no tangents defined
+        */
+        curvePathWithoutTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number, coordinate: number, animationName: string): string;
+        /**
+         * Calculates the curve data and control points for animation
+        */
         curvePathWithTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number, coordinate: number, animationName: string): string;
+        /**
+         * Calculates a curve path from predefined easing function
+        */
         curvePath(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, easingFunction: BABYLON.EasingFunction): string;
+        /**
+         * Sets the proper SVG Keyframe points
+        */
         setKeyframePoint(controlPoints: BABYLON.Vector2[], index: number, keyframesCount: number): void;
         interpolateControlPoints(p0: BABYLON.Vector2, p1: BABYLON.Vector2, u: number, p2: BABYLON.Vector2, v: number, p3: BABYLON.Vector2): BABYLON.Vector2[] | undefined;
-        deselectAnimation(): void;
+        deselectAnimation: () => void;
         /**
-         * Core functions
-         * This section handles main Curve Editor Functions.
+         * Remove all curves from canvas
+        */
+        cleanCanvas: () => void;
+        /**
+         * Selects the animation and renders the curve
+         */
+        selectAnimation: (animation: BABYLON.Animation, coordinate?: SelectedCoordinate | undefined) => void;
+        /**
+         * Set the state for the last selected keyframe
+         */
+        postSelectionEvents: () => void;
+        /**
+         * Set main animatable to play or pause the animation
          */
-        selectAnimation(animation: BABYLON.Animation, coordinate?: SelectedCoordinate): void;
         setMainAnimatable(): void;
         isAnimationPlaying(): boolean;
         stopAnimation(): void;
-        setIsLooping(): void;
-        setFramesPerSecond(fps: number): void;
-        analizeAnimationForLerp(animation: BABYLON.Animation | null): boolean;
+        setIsLooping: () => void;
+        setFramesPerSecond: (fps: number) => void;
+        /**
+        * Check if the animation has easing predefined
+        */
+        analyzeAnimationForLerp(animation: BABYLON.Animation | null): boolean;
         /**
          * Timeline
          * This section controls the timeline.
          */
-        changeCurrentFrame(frame: number): void;
-        setCanvasPosition(frame: number): void;
-        setCurrentFrame(direction: number): void;
-        changeAnimationLimit(limit: number): void;
-        updateFrameInKeyFrame(frame: number, index: number): void;
-        playPause(direction: number): void;
+        changeCurrentFrame: (frame: number) => void;
+        /**
+         * Calculate the value of the selected frame in curve
+         */
+        calculateCurrentPointInCurve: (frame: number) => number | undefined;
+        /**
+         * Center the position the canvas depending on Keyframe value and frame
+         */
+        setCanvasPosition: (keyframe: BABYLON.IAnimationKey) => void;
+        setCurrentFrame: (frame: number) => void;
+        /**
+         * Change the timeline animation frame limit
+         */
+        changeAnimationLimit: (limit: number) => void;
+        /**
+         * Update the frame in the selected Keyframe
+         */
+        updateFrameInKeyFrame: (frame: number, index: number) => void;
+        playPause: (direction: number) => void;
+        /**
+        * Set the frame to selected position on canvas
+        */
         moveFrameTo(e: React.MouseEvent<SVGRectElement, MouseEvent>): void;
         registerObs(): void;
-        componentWillUnmount(): void;
         isCurrentFrame(frame: number): boolean;
+        setPanningY: (panningY: number) => void;
+        setPanningX: (panningX: number) => void;
+        canvasPositionEnded: () => void;
+        setNotificationMessage: (message: string) => void;
+        frameSelectedKeyframes: () => void;
+        /**
+         * Handle the frames quantity and scale on Window resize width
+         */
+        onWindowResizeWidth: () => void;
+        onWindowEndResize: (framesResized: number) => void;
+        onTimelineResize: () => void;
         render(): JSX.Element;
     }
 }
@@ -2849,10 +3139,10 @@ declare module INSPECTOR {
         private _isCurveEditorOpen;
         private _animationGroup;
         constructor(props: ITargetedAnimationGridComponentProps);
-        onOpenAnimationCurveEditor(): void;
-        onCloseAnimationCurveEditor(window: Window | null): void;
-        playOrPause(): void;
-        deleteAnimation(): void;
+        onOpenAnimationCurveEditor: () => void;
+        onCloseAnimationCurveEditor: (window: Window | null) => void;
+        playOrPause: () => void;
+        deleteAnimation: () => void;
         render(): JSX.Element;
     }
 }
@@ -3483,6 +3773,9 @@ declare module INSPECTOR {
         offset: number;
         onCurrentFrameChange: (frame: number) => void;
     }
+    /**
+     * Renders the Playhead
+     */
     export class Playhead extends React.Component<IPlayheadProps> {
         private _direction;
         private _active;

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 845 - 257
dist/preview release/inspector/babylon.inspector.module.d.ts


+ 6 - 6
dist/preview release/materialsLibrary/babylon.cellMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 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__, "cellPixelShader", function() { return cellPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -434,7 +434,7 @@ var cellPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "cellVertexShader", function() { return cellVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -468,7 +468,7 @@ var cellVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CellMaterial", function() { return CellMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _cell_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./cell.fragment */ "./cell/cell.fragment.ts");
 /* harmony import */ var _cell_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./cell.vertex */ "./cell/cell.vertex.ts");
@@ -810,14 +810,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.cellMaterial.js.map


+ 5 - 5
dist/preview release/materialsLibrary/babylon.customMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -403,7 +403,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ShaderSpecialParts", function() { return ShaderSpecialParts; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CustomMaterial", function() { return CustomMaterial; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -661,7 +661,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ShaderAlebdoParts", function() { return ShaderAlebdoParts; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PBRCustomMaterial", function() { return PBRCustomMaterial; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -924,14 +924,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.customMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.fireMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 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__, "firePixelShader", function() { return firePixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -428,7 +428,7 @@ var firePixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fireVertexShader", function() { return fireVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -459,7 +459,7 @@ var fireVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FireMaterial", function() { return FireMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _fire_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fire.fragment */ "./fire/fire.fragment.ts");
 /* harmony import */ var _fire_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fire.vertex */ "./fire/fire.vertex.ts");
@@ -838,14 +838,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.fireMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.furMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 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__, "furPixelShader", function() { return furPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -434,7 +434,7 @@ var furPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "furVertexShader", function() { return furVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -468,7 +468,7 @@ var furVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FurMaterial", function() { return FurMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _fur_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fur.fragment */ "./fur/fur.fragment.ts");
 /* harmony import */ var _fur_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./fur.vertex */ "./fur/fur.vertex.ts");
@@ -1000,14 +1000,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.furMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.gradientMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 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__, "gradientPixelShader", function() { return gradientPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -434,7 +434,7 @@ var gradientPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "gradientVertexShader", function() { return gradientVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -468,7 +468,7 @@ var gradientVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GradientMaterial", function() { return GradientMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _gradient_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./gradient.fragment */ "./gradient/gradient.fragment.ts");
 /* harmony import */ var _gradient_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./gradient.vertex */ "./gradient/gradient.vertex.ts");
@@ -779,14 +779,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.gradientMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.gridMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 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__, "gridPixelShader", function() { return gridPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -425,7 +425,7 @@ var gridPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "gridVertexShader", function() { return gridVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -452,7 +452,7 @@ var gridVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GridMaterial", function() { return GridMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _grid_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./grid.fragment */ "./grid/grid.fragment.ts");
 /* harmony import */ var _grid_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./grid.vertex */ "./grid/grid.vertex.ts");
@@ -753,14 +753,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.gridMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.lavaMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -417,7 +417,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "lavaPixelShader", function() { return lavaPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -451,7 +451,7 @@ var lavaPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "lavaVertexShader", function() { return lavaVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -485,7 +485,7 @@ var lavaVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LavaMaterial", function() { return LavaMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _lava_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./lava.fragment */ "./lava/lava.fragment.ts");
 /* harmony import */ var _lava_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./lava.vertex */ "./lava/lava.vertex.ts");
@@ -898,14 +898,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.lavaMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.mixMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mixPixelShader", function() { return mixPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -480,7 +480,7 @@ var mixPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mixVertexShader", function() { return mixVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -514,7 +514,7 @@ var mixVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MixMaterial", function() { return MixMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _mix_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./mix.fragment */ "./mix/mix.fragment.ts");
 /* harmony import */ var _mix_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./mix.vertex */ "./mix/mix.vertex.ts");
@@ -1003,14 +1003,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.mixMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.normalMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "normalPixelShader", function() { return normalPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -480,7 +480,7 @@ var normalPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "normalVertexShader", function() { return normalVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -514,7 +514,7 @@ var normalVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NormalMaterial", function() { return NormalMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _normal_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./normal.fragment */ "./normal/normal.fragment.ts");
 /* harmony import */ var _normal_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./normal.vertex */ "./normal/normal.vertex.ts");
@@ -843,14 +843,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.normalMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shadowOnlyPixelShader", function() { return shadowOnlyPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -479,7 +479,7 @@ var shadowOnlyPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shadowOnlyVertexShader", function() { return shadowOnlyVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -513,7 +513,7 @@ var shadowOnlyVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ShadowOnlyMaterial", function() { return ShadowOnlyMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _shadowOnly_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./shadowOnly.fragment */ "./shadowOnly/shadowOnly.fragment.ts");
 /* harmony import */ var _shadowOnly_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./shadowOnly.vertex */ "./shadowOnly/shadowOnly.vertex.ts");
@@ -760,14 +760,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.simpleMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "simplePixelShader", function() { return simplePixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -480,7 +480,7 @@ var simplePixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "simpleVertexShader", function() { return simpleVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -514,7 +514,7 @@ var simpleVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleMaterial", function() { return SimpleMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _simple_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./simple.fragment */ "./simple/simple.fragment.ts");
 /* harmony import */ var _simple_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./simple.vertex */ "./simple/simple.vertex.ts");
@@ -801,14 +801,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.simpleMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.skyMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skyPixelShader", function() { return skyPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -473,7 +473,7 @@ var skyPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skyVertexShader", function() { return skyVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -500,7 +500,7 @@ var skyVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SkyMaterial", function() { return SkyMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _sky_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sky.fragment */ "./sky/sky.fragment.ts");
 /* harmony import */ var _sky_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sky.vertex */ "./sky/sky.vertex.ts");
@@ -833,14 +833,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.skyMaterial.js.map


+ 6 - 6
dist/preview release/materialsLibrary/babylon.terrainMaterial.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-materials"] = factory(require("babylonjs"));
 	else
 		root["MATERIALS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -446,7 +446,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "terrainPixelShader", function() { return terrainPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -480,7 +480,7 @@ var terrainPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "terrainVertexShader", function() { return terrainVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Materials/effect");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/decorators");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -514,7 +514,7 @@ var terrainVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TerrainMaterial", function() { return TerrainMaterial; });
 /* 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/Materials/effect");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/decorators");
 /* 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 _terrain_fragment__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./terrain.fragment */ "./terrain/terrain.fragment.ts");
 /* harmony import */ var _terrain_vertex__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./terrain.vertex */ "./terrain/terrain.vertex.ts");
@@ -931,14 +931,14 @@ babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 
 /***/ }),
 
-/***/ "babylonjs/Materials/effect":
+/***/ "babylonjs/Misc/decorators":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Materials_effect__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_decorators__;
 
 /***/ })
 

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.terrainMaterial.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 7 - 7
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.min.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 38 - 29
dist/preview release/materialsLibrary/babylon.waterMaterial.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.waterMaterial.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylon.waterMaterial.min.js


+ 25 - 21
dist/preview release/materialsLibrary/babylonjs.materials.d.ts

@@ -895,61 +895,65 @@ declare module BABYLON {
         private _maxSimultaneousLights;
         maxSimultaneousLights: number;
         /**
-        * @param {number}: Represents the wind force
-        */
+         * Defines the wind force.
+         */
         windForce: number;
         /**
-        * @param {Vector2}: The direction of the wind in the plane (X, Z)
-        */
+         * Defines the direction of the wind in the plane (X, Z).
+         */
         windDirection: BABYLON.Vector2;
         /**
-        * @param {number}: Wave height, represents the height of the waves
-        */
+         * Defines the height of the waves.
+         */
         waveHeight: number;
         /**
-        * @param {number}: Bump height, represents the bump height related to the bump map
-        */
+         * Defines the bump height related to the bump map.
+         */
         bumpHeight: number;
         /**
-         * @param {boolean}: Add a smaller moving bump to less steady waves.
+         * Defines wether or not: to add a smaller moving bump to less steady waves.
          */
         private _bumpSuperimpose;
         bumpSuperimpose: boolean;
         /**
-         * @param {boolean}: Color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
+         * Defines wether or not color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
          */
         private _fresnelSeparate;
         fresnelSeparate: boolean;
         /**
-         * @param {boolean}: bump Waves modify the reflection.
+         * Defines wether or not bump Wwves modify the reflection.
          */
         private _bumpAffectsReflection;
         bumpAffectsReflection: boolean;
         /**
-        * @param {number}: The water color blended with the refraction (near)
-        */
+         * Defines the water color blended with the refraction (near).
+         */
         waterColor: BABYLON.Color3;
         /**
-        * @param {number}: The blend factor related to the water color
-        */
+         * Defines the blend factor related to the water color.
+         */
         colorBlendFactor: number;
         /**
-         * @param {number}: The water color blended with the reflection (far)
+         * Defines the water color blended with the reflection (far).
          */
         waterColor2: BABYLON.Color3;
         /**
-         * @param {number}: The blend factor related to the water color (reflection, far)
+         * Defines the blend factor related to the water color (reflection, far).
          */
         colorBlendFactor2: number;
         /**
-        * @param {number}: Represents the maximum length of a wave
-        */
+         * Defines the maximum length of a wave.
+         */
         waveLength: number;
         /**
-        * @param {number}: Defines the waves speed
-        */
+         * Defines the waves speed.
+         */
         waveSpeed: number;
         /**
+         * Defines the number of times waves are repeated. This is typically used to adjust waves count according to the ground's size where the material is applied on.
+         */
+        waveCount: number;
+        /**
          * Sets or gets whether or not automatic clipping should be enabled or not. Setting to true will save performances and
          * will avoid calculating useless pixels in the pixel shader of the water material.
          */

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 80 - 71
dist/preview release/materialsLibrary/babylonjs.materials.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylonjs.materials.js.map


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/materialsLibrary/babylonjs.materials.min.js


+ 50 - 42
dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts

@@ -1406,61 +1406,65 @@ declare module "babylonjs-materials/water/waterMaterial" {
         private _maxSimultaneousLights;
         maxSimultaneousLights: number;
         /**
-        * @param {number}: Represents the wind force
-        */
+         * Defines the wind force.
+         */
         windForce: number;
         /**
-        * @param {Vector2}: The direction of the wind in the plane (X, Z)
-        */
+         * Defines the direction of the wind in the plane (X, Z).
+         */
         windDirection: Vector2;
         /**
-        * @param {number}: Wave height, represents the height of the waves
-        */
+         * Defines the height of the waves.
+         */
         waveHeight: number;
         /**
-        * @param {number}: Bump height, represents the bump height related to the bump map
-        */
+         * Defines the bump height related to the bump map.
+         */
         bumpHeight: number;
         /**
-         * @param {boolean}: Add a smaller moving bump to less steady waves.
+         * Defines wether or not: to add a smaller moving bump to less steady waves.
          */
         private _bumpSuperimpose;
         bumpSuperimpose: boolean;
         /**
-         * @param {boolean}: Color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
+         * Defines wether or not color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
          */
         private _fresnelSeparate;
         fresnelSeparate: boolean;
         /**
-         * @param {boolean}: bump Waves modify the reflection.
+         * Defines wether or not bump Wwves modify the reflection.
          */
         private _bumpAffectsReflection;
         bumpAffectsReflection: boolean;
         /**
-        * @param {number}: The water color blended with the refraction (near)
-        */
+         * Defines the water color blended with the refraction (near).
+         */
         waterColor: Color3;
         /**
-        * @param {number}: The blend factor related to the water color
-        */
+         * Defines the blend factor related to the water color.
+         */
         colorBlendFactor: number;
         /**
-         * @param {number}: The water color blended with the reflection (far)
+         * Defines the water color blended with the reflection (far).
          */
         waterColor2: Color3;
         /**
-         * @param {number}: The blend factor related to the water color (reflection, far)
+         * Defines the blend factor related to the water color (reflection, far).
          */
         colorBlendFactor2: number;
         /**
-        * @param {number}: Represents the maximum length of a wave
-        */
+         * Defines the maximum length of a wave.
+         */
         waveLength: number;
         /**
-        * @param {number}: Defines the waves speed
-        */
+         * Defines the waves speed.
+         */
         waveSpeed: number;
         /**
+         * Defines the number of times waves are repeated. This is typically used to adjust waves count according to the ground's size where the material is applied on.
+         */
+        waveCount: number;
+        /**
          * Sets or gets whether or not automatic clipping should be enabled or not. Setting to true will save performances and
          * will avoid calculating useless pixels in the pixel shader of the water material.
          */
@@ -2477,61 +2481,65 @@ declare module BABYLON {
         private _maxSimultaneousLights;
         maxSimultaneousLights: number;
         /**
-        * @param {number}: Represents the wind force
-        */
+         * Defines the wind force.
+         */
         windForce: number;
         /**
-        * @param {Vector2}: The direction of the wind in the plane (X, Z)
-        */
+         * Defines the direction of the wind in the plane (X, Z).
+         */
         windDirection: BABYLON.Vector2;
         /**
-        * @param {number}: Wave height, represents the height of the waves
-        */
+         * Defines the height of the waves.
+         */
         waveHeight: number;
         /**
-        * @param {number}: Bump height, represents the bump height related to the bump map
-        */
+         * Defines the bump height related to the bump map.
+         */
         bumpHeight: number;
         /**
-         * @param {boolean}: Add a smaller moving bump to less steady waves.
+         * Defines wether or not: to add a smaller moving bump to less steady waves.
          */
         private _bumpSuperimpose;
         bumpSuperimpose: boolean;
         /**
-         * @param {boolean}: Color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
+         * Defines wether or not color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
          */
         private _fresnelSeparate;
         fresnelSeparate: boolean;
         /**
-         * @param {boolean}: bump Waves modify the reflection.
+         * Defines wether or not bump Wwves modify the reflection.
          */
         private _bumpAffectsReflection;
         bumpAffectsReflection: boolean;
         /**
-        * @param {number}: The water color blended with the refraction (near)
-        */
+         * Defines the water color blended with the refraction (near).
+         */
         waterColor: BABYLON.Color3;
         /**
-        * @param {number}: The blend factor related to the water color
-        */
+         * Defines the blend factor related to the water color.
+         */
         colorBlendFactor: number;
         /**
-         * @param {number}: The water color blended with the reflection (far)
+         * Defines the water color blended with the reflection (far).
          */
         waterColor2: BABYLON.Color3;
         /**
-         * @param {number}: The blend factor related to the water color (reflection, far)
+         * Defines the blend factor related to the water color (reflection, far).
          */
         colorBlendFactor2: number;
         /**
-        * @param {number}: Represents the maximum length of a wave
-        */
+         * Defines the maximum length of a wave.
+         */
         waveLength: number;
         /**
-        * @param {number}: Defines the waves speed
-        */
+         * Defines the waves speed.
+         */
         waveSpeed: number;
         /**
+         * Defines the number of times waves are repeated. This is typically used to adjust waves count according to the ground's size where the material is applied on.
+         */
+        waveCount: number;
+        /**
          * Sets or gets whether or not automatic clipping should be enabled or not. Setting to true will save performances and
          * will avoid calculating useless pixels in the pixel shader of the water material.
          */

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.js


+ 3 - 0
dist/preview release/nodeEditor/babylon.nodeEditor.max.js

@@ -66662,6 +66662,9 @@ var GraphCanvasComponent = /** @class */ (function (_super) {
         // Update graph
         var dagreNodes = graph.nodes().map(function (node) { return graph.node(node); });
         dagreNodes.forEach(function (dagreNode) {
+            if (!dagreNode) {
+                return;
+            }
             if (dagreNode.type === "node") {
                 for (var _i = 0, _a = _this._nodes; _i < _a.length; _i++) {
                     var node = _a[_i];

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


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

@@ -1 +1 @@
-{"thinEngineOnly":118472,"engineOnly":154912,"sceneOnly":521352,"minGridMaterial":662427,"minStandardMaterial":818199}
+{"thinEngineOnly":118472,"engineOnly":154912,"sceneOnly":521352,"minGridMaterial":662536,"minStandardMaterial":818308}

+ 10 - 0
dist/preview release/viewer/babylon.module.d.ts

@@ -18490,6 +18490,11 @@ declare module "babylonjs/Lights/Shadows/shadowGenerator" {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.
@@ -98590,6 +98595,11 @@ declare module BABYLON {
         /** @hidden */
         static _SceneComponentInitialization: (scene: Scene) => void;
         /**
+         * Gets or sets the size of the texture what stores the shadows
+         */
+        get mapSize(): number;
+        set mapSize(size: number);
+        /**
          * Creates a ShadowGenerator object.
          * A ShadowGenerator is the required tool to use the shadows.
          * Each light casting shadows needs to use its own ShadowGenerator.

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2 - 2
dist/preview release/viewer/babylon.viewer.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -169,6 +169,7 @@
 - Added the `shadowOnly` property to the `BackgroundMaterial` class ([Popov72](https://github.com/Popov72))
 - Added support for lightmaps in unlit PBR materials ([Popov72](https://github.com/Popov72))
 - Added `muted` setting to `VideoTexture`, fix autoplay in Chrome ([simonihmig](https://github.com/simonihmig))
+- Added `waveCount` to `WaterMaterial` used to adjust waves count according to the ground's size where the material is applied on ([julien-moreau](https://github.com/julien-moreau))
 
 ### Meshes
 
@@ -328,8 +329,10 @@
 - Fix wrong winding when applying a transform matrix on VertexData ([Popov72](https://github.com/Popov72))
 - Fix exporting vertex color of mesh with `StandardMaterial` when exporting to glTF ([Drigax](https://github.com/drigax))
 - Changed use of mousemove to pointermove in freeCameraMouseInput and flyCameraMouseInput to fix issue with Firefox ([PolygonalSun](https://github.com/PolygonalSun))
+- Fixed `TriPlanarMaterial` to compute the right world normals ([julien-moreau](https://github.com/julien-moreau))
 
 ## Breaking changes
+
 - `FollowCamera.target` was renamed to `FollowCamera.meshTarget` to not be in conflict with `TargetCamera.target` ([Deltakosh](https://github.com/deltakosh))
 - `EffectRenderer.render` now takes a `RenderTargetTexture` or an `InternalTexture` as the output texture and only a single `EffectWrapper` for its first argument ([Popov72](https://github.com/Popov72))
 - Sound's `updateOptions` takes `options.length` and `options.offset` as seconds and not milliseconds ([RaananW](https://github.com/RaananW))

+ 38 - 33
inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx

@@ -15,11 +15,14 @@ interface IAddAnimationProps {
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
     setNotificationMessage: (message: string) => void;
     finishedUpdate: () => void;
-    addedNewAnimation: () => void;
+    addedNewAnimation: (animation: Animation) => void;
     fps: number;
     selectedToUpdate?: Animation | undefined;
 }
 
+/**
+ * Controls the creation of a new animation
+ */
 export class AddAnimation extends React.Component<
     IAddAnimationProps,
     {
@@ -47,17 +50,17 @@ export class AddAnimation extends React.Component<
         };
     }
 
-    componentWillReceiveProps(nextProps: IAddAnimationProps) {
-        if (nextProps.selectedToUpdate !== undefined && nextProps.selectedToUpdate !== this.props.selectedToUpdate) {
-            this.setState(this.setInitialState(nextProps.selectedToUpdate));
+    componentDidUpdate(prevProps: IAddAnimationProps, prevState: any) {
+        if (this.props.selectedToUpdate !== undefined && this.props.selectedToUpdate !== prevProps.selectedToUpdate) {
+            this.setState(this.setInitialState(this.props.selectedToUpdate));
         } else {
-            if (nextProps.isOpen === true && nextProps.isOpen !== this.props.isOpen) {
+            if (this.props.isOpen === true && this.props.isOpen !== prevProps.isOpen) {
                 this.setState(this.setInitialState());
             }
         }
     }
 
-    updateAnimation() {
+    updateAnimation = () => {
         if (this.props.selectedToUpdate !== undefined) {
             const oldNameValue = this.props.selectedToUpdate.name;
             this.props.selectedToUpdate.name = this.state.animationName;
@@ -73,7 +76,7 @@ export class AddAnimation extends React.Component<
 
             this.props.finishedUpdate();
         }
-    }
+    };
 
     getTypeAsString(type: number) {
         switch (type) {
@@ -96,7 +99,7 @@ export class AddAnimation extends React.Component<
         }
     }
 
-    addAnimation() {
+    addAnimation = () => {
         if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
             let matchTypeTargetProperty = this.state.animationTargetProperty.split(".");
             let animationDataType = this.state.animationType;
@@ -109,22 +112,22 @@ export class AddAnimation extends React.Component<
                 if (match) {
                     switch (match.constructor.name) {
                         case "Vector2":
-                            animationDataType === Animation.ANIMATIONTYPE_VECTOR2 ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_VECTOR2;
                             break;
                         case "Vector3":
-                            animationDataType === Animation.ANIMATIONTYPE_VECTOR3 ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_VECTOR3;
                             break;
                         case "Quaternion":
-                            animationDataType === Animation.ANIMATIONTYPE_QUATERNION ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_QUATERNION;
                             break;
                         case "Color3":
-                            animationDataType === Animation.ANIMATIONTYPE_COLOR3 ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_COLOR3;
                             break;
                         case "Color4":
-                            animationDataType === Animation.ANIMATIONTYPE_COLOR4 ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_COLOR4;
                             break;
                         case "Size":
-                            animationDataType === Animation.ANIMATIONTYPE_SIZE ? (matched = true) : (matched = false);
+                            matched = animationDataType === Animation.ANIMATIONTYPE_SIZE;
                             break;
                     }
                 } else {
@@ -162,7 +165,7 @@ export class AddAnimation extends React.Component<
                         const updatedCollection = [...this.props.entity.animations, animation];
                         this.raiseOnPropertyChanged(updatedCollection, store);
                         this.props.entity.animations = updatedCollection;
-                        this.props.addedNewAnimation();
+                        this.props.addedNewAnimation(animation);
                         //Cleaning form fields
                         this.setState({
                             animationName: "",
@@ -179,7 +182,7 @@ export class AddAnimation extends React.Component<
         } else {
             this.props.setNotificationMessage(`You need to provide a name and target property.`);
         }
-    }
+    };
 
     raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
         if (!this.props.onPropertyChangedObservable) {
@@ -207,49 +210,51 @@ export class AddAnimation extends React.Component<
         });
     }
 
-    handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handlePathChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
-        this.setState({ animationName: event.target.value.trim() });
-    }
+        this.setState({ animationTargetPath: event.target.value.trim() });
+    };
 
-    handlePathChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
-        this.setState({ animationTargetPath: event.target.value.trim() });
-    }
+        this.setState({ animationName: event.target.value.trim() });
+    };
 
-    handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+    handleTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
         event.preventDefault();
         this.setState({ animationType: parseInt(event.target.value) });
-    }
+    };
 
-    handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
+    handlePropertyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
         event.preventDefault();
         this.setState({ animationTargetProperty: event.target.value });
-    }
+    };
 
-    handleLoopModeChange(event: React.ChangeEvent<HTMLSelectElement>) {
+    handleLoopModeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
         event.preventDefault();
         this.setState({ loopMode: parseInt(event.target.value) });
-    }
+    };
 
     render() {
+        const confirmLabel = this.state.isUpdating ? "Update" : "Create";
+        const confirmHandleOnClick = this.state.isUpdating ? this.updateAnimation : this.addAnimation;
         return (
             <div className="new-animation" style={{ display: this.props.isOpen ? "block" : "none" }}>
                 <div className="sub-content">
                     <div className="label-input">
                         <label>Display Name</label>
-                        <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
+                        <input type="text" value={this.state.animationName} onChange={this.handleNameChange}></input>
                     </div>
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Property</label>
-                            <input type="text" value={this.state.animationTargetProperty} onChange={(e) => this.handlePropertyChange(e)}></input>
+                            <input type="text" value={this.state.animationTargetProperty} onChange={this.handlePropertyChange}></input>
                         </div>
                     )}
                     {this.state.isUpdating ? null : (
                         <div className="label-input">
                             <label>Type</label>
-                            <select onChange={(e) => this.handleTypeChange(e)} value={this.state.animationType}>
+                            <select onChange={this.handleTypeChange} value={this.state.animationType}>
                                 {/* <option value={Animation.ANIMATIONTYPE_COLOR3}>Color3</option>
                 <option value={Animation.ANIMATIONTYPE_COLOR4}>Color4</option> */}
                                 <option value={Animation.ANIMATIONTYPE_FLOAT}>Float</option>
@@ -263,14 +268,14 @@ export class AddAnimation extends React.Component<
                     )}
                     <div className="label-input">
                         <label>Loop Mode</label>
-                        <select onChange={(e) => this.handleLoopModeChange(e)} value={this.state.loopMode}>
+                        <select onChange={this.handleLoopModeChange} value={this.state.loopMode}>
                             <option value={Animation.ANIMATIONLOOPMODE_CYCLE}>Cycle</option>
                             <option value={Animation.ANIMATIONLOOPMODE_RELATIVE}>Relative</option>
                             <option value={Animation.ANIMATIONLOOPMODE_CONSTANT}>Constant</option>
                         </select>
                     </div>
                     <div className="confirm-buttons">
-                        <ButtonLineComponent label={this.state.isUpdating ? "Update" : "Create"} onClick={this.state.isUpdating ? () => this.updateAnimation() : () => this.addAnimation()} />
+                        <ButtonLineComponent label={confirmLabel} onClick={confirmHandleOnClick} />
                         {this.props.entity.animations?.length !== 0 ? <ButtonLineComponent label={"Cancel"} onClick={this.props.close} /> : null}
                     </div>
                 </div>

+ 85 - 50
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -1,57 +1,92 @@
-import * as React from 'react';
-import { Vector2 } from 'babylonjs/Maths/math.vector';
+import * as React from "react";
+import { Vector2 } from "babylonjs/Maths/math.vector";
 
 interface IAnchorSvgPointProps {
-  control: Vector2;
-  anchor: Vector2;
-  active: boolean;
-  type: string;
-  index: string;
-  selected: boolean;
-  selectControlPoint: (id: string) => void;
+    control: Vector2;
+    anchor: Vector2;
+    active: boolean;
+    type: string;
+    index: string;
+    selected: boolean;
+    selectControlPoint: (id: string) => void;
+    framesInCanvasView: { from: number; to: number };
 }
 
-export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps> {
-  constructor(props: IAnchorSvgPointProps) {
-    super(props);
-  }
+/**
+ * Renders the control point to a keyframe.
+ */
+export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { visiblePoint: Vector2 }> {
+    constructor(props: IAnchorSvgPointProps) {
+        super(props);
+        this.state = { visiblePoint: this.setVisiblePoint() };
+    }
 
-  select() {
-    this.props.selectControlPoint(this.props.type);
-  }
+    componentDidUpdate(prevProps: IAnchorSvgPointProps, prevState: any) {
+        if (prevProps.control !== this.props.control) {
+            this.setState({ visiblePoint: this.setVisiblePoint() });
+        }
+    }
 
-  render() {
-    return (
-      <>
-        <svg
-          x={this.props.control.x}
-          y={this.props.control.y}
-          style={{ overflow: 'visible' }}
-          onClick={() => this.select()}
-        >
-          <circle
-            type={this.props.type}
-            data-id={this.props.index}
-            className={`draggable control-point ${
-              this.props.active ? 'active' : ''
-            }`}
-            cx='0'
-            cy='0'
-            r='0.7%'
-            stroke='white'
-            strokeWidth={this.props.selected ? 0 : 0}
-            fill={this.props.active ? '#e9db1e' : 'white'}
-          />
-        </svg>
-        <line
-          className={`control-point ${this.props.active ? 'active' : ''}`}
-          x1={this.props.anchor.x}
-          y1={this.props.anchor.y}
-          x2={this.props.control.x}
-          y2={this.props.control.y}
-          strokeWidth='0.8%'
-        />
-      </>
-    );
-  }
+    select = () => {
+        this.props.selectControlPoint(this.props.type);
+    };
+
+    setVisiblePoint() {
+        const quarterDistance = (this.props.framesInCanvasView.to - this.props.framesInCanvasView.from) / 10;
+        const distanceOnFlat = Math.abs(this.props.anchor.x - this.props.control.x);
+        const currentDistance = Vector2.Distance(this.props.anchor, this.props.control);
+        const percentageChange = ((currentDistance - distanceOnFlat) * 100) / currentDistance;
+        const updateAmount = quarterDistance - (quarterDistance * percentageChange) / 100;
+        return Vector2.Lerp(this.props.anchor, this.props.control, updateAmount);
+    }
+
+    render() {
+        const visibleCircleClass = `draggable control-point ${this.props.active ? "active" : ""}`;
+        const nonVisibleCircleClass = `control-point ${this.props.active ? "active" : ""}`;
+        const strokeVisibleCircle = this.props.selected ? 1 : 0;
+        const visibleCircle = this.props.selected ? "#ffc017" : "black";
+        return (
+            <>
+                <line
+                    className={`control-point ${this.props.active ? "active" : ""}`}
+                    x1={this.props.anchor.x}
+                    y1={this.props.anchor.y}
+                    x2={this.state.visiblePoint.x}
+                    y2={this.state.visiblePoint.y}
+                    strokeWidth="0.8%"
+                />
+                <svg
+                    x={this.state.visiblePoint.x}
+                    y={this.state.visiblePoint.y}
+                    style={{ overflow: "visible" }}
+                    onClick={this.select}
+                >
+                    <circle
+                        type={this.props.type}
+                        data-id={this.props.index}
+                        className={visibleCircleClass}
+                        cx="0"
+                        cy="0"
+                        r="0.75%"
+                        stroke="aqua"
+                        strokeWidth={strokeVisibleCircle}
+                        fill={visibleCircle}
+                    />
+                </svg>
+                <svg x={this.props.control.x} y={this.props.control.y} style={{ overflow: "visible", display: "none" }}>
+                    <circle
+                        type={this.props.type}
+                        data-id={this.props.index}
+                        className={nonVisibleCircleClass}
+                        cx="0"
+                        cy="0"
+                        r="0.7%"
+                        stroke="white"
+                        strokeWidth={0}
+                        fill={"white"}
+                    />
+                </svg>
+            </>
+        );
+    }
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1140 - 363
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx


+ 43 - 16
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx

@@ -11,18 +11,21 @@ import { LineContainerComponent } from "../../../lineContainerComponent";
 import { TextLineComponent } from "../../../lines/textLineComponent";
 import { SliderLineComponent } from "../../../lines/sliderLineComponent";
 import { LockObject } from "../lockObject";
-import { GlobalState } from '../../../../globalState';
-import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
+import { GlobalState } from "../../../../globalState";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
 
 interface IAnimationGroupGridComponentProps {
     globalState: GlobalState;
-    animationGroup: AnimationGroup,
-    scene: Scene,
-    lockObject: LockObject,
-    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+    animationGroup: AnimationGroup;
+    scene: Scene;
+    lockObject: LockObject;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
-export class AnimationGroupGridComponent extends React.Component<IAnimationGroupGridComponentProps, { playButtonText: string, currentFrame: number }> {
+export class AnimationGroupGridComponent extends React.Component<
+    IAnimationGroupGridComponentProps,
+    { playButtonText: string; currentFrame: number }
+> {
     private _onAnimationGroupPlayObserver: Nullable<Observer<AnimationGroup>>;
     private _onAnimationGroupPauseObserver: Nullable<Observer<AnimationGroup>>;
     private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
@@ -44,7 +47,6 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
     }
 
     disconnect(animationGroup: AnimationGroup) {
-
         if (this._onAnimationGroupPlayObserver) {
             animationGroup.onAnimationGroupPlayObservable.remove(this._onAnimationGroupPlayObserver);
             this._onAnimationGroupPlayObserver = null;
@@ -105,7 +107,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
             animationGroup.pause();
         } else {
             this.setState({ playButtonText: "Pause" });
-            this.props.scene.animationGroups.forEach(grp => grp.pause());
+            this.props.scene.animationGroups.forEach((grp) => grp.pause());
             animationGroup.play(true);
         }
     }
@@ -127,21 +129,46 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
     render() {
         const animationGroup = this.props.animationGroup;
 
-        const playButtonText = animationGroup.isPlaying ? "Pause" : "Play"
+        const playButtonText = animationGroup.isPlaying ? "Pause" : "Play";
 
         return (
-            <div className="pane">                
+            <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
                     <TextLineComponent label="Class" value={animationGroup.getClassName()} />
-                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={animationGroup} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                    <TextInputLineComponent
+                        lockObject={this.props.lockObject}
+                        label="Name"
+                        target={animationGroup}
+                        propertyName="name"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                    />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="CONTROLS">
                     <ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
-                    <SliderLineComponent label="Speed ratio" minimum={0} maximum={10} step={0.1} target={animationGroup} propertyName="speedRatio" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent ref={this.timelineRef} label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 1000.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
+                    <SliderLineComponent
+                        label="Speed ratio"
+                        minimum={0}
+                        maximum={10}
+                        step={0.1}
+                        target={animationGroup}
+                        propertyName="speedRatio"
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                    />
+                    <SliderLineComponent
+                        ref={this.timelineRef}
+                        label="Current frame"
+                        minimum={animationGroup.from}
+                        maximum={animationGroup.to}
+                        step={(animationGroup.to - animationGroup.from) / 1000.0}
+                        directValue={this.state.currentFrame}
+                        onInput={(value) => this.onCurrentFrameChange(value)}
+                    />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="INFOS">
-                    <TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />
+                    <TextLineComponent
+                        label="Animation count"
+                        value={animationGroup.targetedAnimations.length.toString()}
+                    />
                     <TextLineComponent label="From" value={animationGroup.from.toFixed(2)} />
                     <TextLineComponent label="To" value={animationGroup.to.toFixed(2)} />
                     <TextLineComponent label="Unique ID" value={animationGroup.uniqueId.toString()} />
@@ -149,4 +176,4 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
             </div>
         );
     }
-}
+}

+ 94 - 18
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx

@@ -45,37 +45,92 @@ interface ItemCoordinate {
     coordinate: SelectedCoordinate;
 }
 
+/**
+ * Renders a list of current animations.
+ */
 export class AnimationListTree extends React.Component<
     IAnimationListTreeProps,
     {
         selectedCoordinate: SelectedCoordinate;
         selectedAnimation: number;
         animationList: Item[] | null;
+        animations: Nullable<Animation[]> | Animation;
     }
 > {
     constructor(props: IAnimationListTreeProps) {
         super(props);
 
+        const animations = this.props.entity instanceof TargetedAnimation ? (this.props.entity as TargetedAnimation).animation : (this.props.entity as IAnimatable).animations;
+
         this.state = {
             selectedCoordinate: 0,
             selectedAnimation: 0,
             animationList: this.generateList(),
+            animations: animations,
         };
     }
 
-    deleteAnimation() {
+    componentDidUpdate(prevProps: IAnimationListTreeProps) {
+        if (this.props.entity instanceof TargetedAnimation) {
+            if ((this.props.entity as TargetedAnimation).animation !== (prevProps.entity as TargetedAnimation).animation) {
+                this.setState({
+                    animationList: this.generateList(),
+                    animations: (this.props.entity as TargetedAnimation).animation,
+                });
+            }
+        } else {
+            if ((this.props.entity as IAnimatable).animations !== (prevProps.entity as IAnimatable).animations) {
+                this.setState({
+                    animationList: this.generateList(),
+                    animations: (this.props.entity as IAnimatable).animations,
+                });
+            }
+        }
+    }
+
+    deleteAnimation = () => {
         let currentSelected = this.props.selected;
         if (this.props.entity instanceof TargetedAnimation) {
             console.log("no animation remove allowed");
         } else {
             let animations = (this.props.entity as IAnimatable).animations;
             if (animations) {
-                let updatedAnimations = animations.filter((anim) => anim !== currentSelected);
-                (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
-                this.props.deselectAnimation();
-                this.setState({ animationList: this.generateList() });
+                if ((this.props.entity as IAnimatable).animations !== null) {
+                    let updatedAnimations = animations.filter((anim) => anim !== currentSelected);
+
+                    const store = (this.props.entity as IAnimatable).animations!;
+                    this.raiseOnPropertyChanged(updatedAnimations, store);
+                    (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
+                    if (updatedAnimations.length !== 0) {
+                        this.setState(
+                            {
+                                animationList: this.generateList(),
+                                animations: (this.props.entity as IAnimatable).animations,
+                            },
+                            () => {
+                                this.props.deselectAnimation();
+                            }
+                        );
+                    } else {
+                        this.props.deselectAnimation();
+                        this.props.empty();
+                    }
+                }
             }
         }
+    };
+
+    raiseOnPropertyChanged(newValue: Animation[], previousValue: Animation[]) {
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.entity,
+            property: "animations",
+            value: newValue,
+            initialValue: previousValue,
+        });
     }
 
     generateList() {
@@ -114,22 +169,35 @@ export class AnimationListTree extends React.Component<
     }
 
     coordinateItem(i: number, animation: Animation, coordinate: string, color: string, selectedCoordinate: SelectedCoordinate) {
+        const setSelectedCoordinate = () => this.setSelectedCoordinate(animation, selectedCoordinate, i);
+        const handleClass = `handle-indicator ${this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"}`;
         return (
-            <li key={`${i}_${coordinate}`} id={`${i}_${coordinate}`} className="property" style={{ color: color }} onClick={() => this.setSelectedCoordinate(animation, selectedCoordinate, i)}>
-                <div className={`handle-indicator ${this.state.selectedCoordinate === selectedCoordinate && this.state.selectedAnimation === i ? "show" : "hide"}`}></div>
+            <li key={`${i}_${coordinate}`} id={`${i}_${coordinate}`} className="property" style={{ color: color }} onClick={setSelectedCoordinate}>
+                <div className={handleClass}></div>
                 {animation.targetProperty} {coordinate.toUpperCase()}
             </li>
         );
     }
 
     typeAnimationItem(animation: Animation, i: number, childrenElements: ItemCoordinate[]) {
+        const editAnimation = () => this.props.editAnimation(animation);
+        const selectAnimation = () => this.props.selectAnimation(animation);
+        const toggle = () => this.toggleProperty(i);
         return (
             <li className={this.props.selected && this.props.selected.name === animation.name ? "property sub active" : "property sub"} key={i}>
-                <div className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`} onClick={() => this.toggleProperty(i)}></div>
-                <p onClick={() => this.props.selectAnimation(animation)}>{animation.targetProperty}</p>
-                <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={() => this.props.editAnimation(animation)} />
-                {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={() => this.deleteAnimation()} /> : <div className="spacer"></div> : null}
-                <ul className={`sub-list ${this.state.animationList && this.state.animationList[i].open ? "" : "hidden"}`}>{childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}</ul>
+                <div className={`animation-arrow ${this.state.animationList && this.state.animationList[i].open ? "" : "flip"}`} onClick={toggle}></div>
+                <p onClick={selectAnimation}>{animation.targetProperty}</p>
+                <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
+                {!((this.props.entity as TargetedAnimation).getClassName() === "TargetedAnimation") ? (
+                    this.props.selected && this.props.selected.name === animation.name ? (
+                        <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={this.deleteAnimation} />
+                    ) : (
+                        <div className="spacer"></div>
+                    )
+                ) : null}
+                <ul className={`sub-list ${this.state.animationList && this.state.animationList[i].open ? "" : "hidden"}`}>
+                    {childrenElements.map((c) => this.coordinateItem(i, animation, c.id, c.color, c.coordinate))}
+                </ul>
             </li>
         );
     }
@@ -137,12 +205,20 @@ export class AnimationListTree extends React.Component<
     setListItem(animation: Animation, i: number) {
         switch (animation.dataType) {
             case Animation.ANIMATIONTYPE_FLOAT:
+                const editAnimation = () => this.props.editAnimation(animation);
+                const selectAnimation = () => this.props.selectAnimation(animation, 0);
                 return (
-                    <li className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"} key={i} onClick={() => this.props.selectAnimation(animation, 0)}>
+                    <li className={this.props.selected && this.props.selected.name === animation.name ? "property active" : "property"} key={i} onClick={selectAnimation}>
                         <div className={`animation-bullet`}></div>
                         <p>{animation.targetProperty}</p>
-                        <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={() => this.props.editAnimation(animation)} />
-                        {!(this.props.entity instanceof TargetedAnimation) ? this.props.selected && this.props.selected.name === animation.name ? <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={() => this.deleteAnimation()} /> : <div className="spacer"></div> : null}
+                        <IconButtonLineComponent tooltip="Options" icon="small animation-options" onClick={editAnimation} />
+                        {!(this.props.entity instanceof TargetedAnimation) ? (
+                            this.props.selected && this.props.selected.name === animation.name ? (
+                                <IconButtonLineComponent tooltip="Remove" icon="small animation-delete" onClick={this.deleteAnimation} />
+                            ) : (
+                                <div className="spacer"></div>
+                            )
+                        ) : null}
                     </li>
                 );
             case Animation.ANIMATIONTYPE_VECTOR2:
@@ -199,9 +275,9 @@ export class AnimationListTree extends React.Component<
             <div className="object-tree">
                 <ul>
                     {this.props.isTargetedAnimation
-                        ? this.setListItem((this.props.entity as TargetedAnimation).animation, 0)
-                        : (this.props.entity as IAnimatable).animations &&
-                          (this.props.entity as IAnimatable).animations?.map((animation, i) => {
+                        ? this.setListItem(this.state.animations as Animation, 0)
+                        : this.state.animations &&
+                          (this.state.animations as Animation[]).map((animation, i) => {
                               return this.setListItem(animation, i);
                           })}
                 </ul>

+ 211 - 316
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx

@@ -1,349 +1,244 @@
-import * as React from 'react';
-
-import { Observable, Observer } from 'babylonjs/Misc/observable';
-import { Scene } from 'babylonjs/scene';
-
-import { PropertyChangedEvent } from '../../../../propertyChangedEvent';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { LineContainerComponent } from '../../../lineContainerComponent';
-import { SliderLineComponent } from '../../../lines/sliderLineComponent';
-import { LockObject } from '../lockObject';
-import { GlobalState } from '../../../../globalState';
-import { Animation } from 'babylonjs/Animations/animation';
-import { Animatable } from 'babylonjs/Animations/animatable';
-import { AnimationPropertiesOverride } from 'babylonjs/Animations/animationPropertiesOverride';
-import { AnimationRange } from 'babylonjs/Animations/animationRange';
-import { CheckBoxLineComponent } from '../../../lines/checkBoxLineComponent';
-import { Nullable } from 'babylonjs/types';
-import { FloatLineComponent } from '../../../lines/floatLineComponent';
-import { TextLineComponent } from '../../../lines/textLineComponent';
-import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
-import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
-import { PopupComponent } from '../../../../popupComponent';
+import * as React from "react";
+
+import { Observable, Observer } from "babylonjs/Misc/observable";
+import { Scene } from "babylonjs/scene";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { SliderLineComponent } from "../../../lines/sliderLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from "../../../../globalState";
+import { Animation } from "babylonjs/Animations/animation";
+import { Animatable } from "babylonjs/Animations/animatable";
+import { AnimationPropertiesOverride } from "babylonjs/Animations/animationPropertiesOverride";
+import { AnimationRange } from "babylonjs/Animations/animationRange";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { Nullable } from "babylonjs/types";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { IAnimatable } from "babylonjs/Animations/animatable.interface";
+import { AnimationCurveEditorComponent } from "../animations/animationCurveEditorComponent";
+import { PopupComponent } from "../../../../popupComponent";
 
 interface IAnimationGridComponentProps {
-  globalState: GlobalState;
-  animatable: IAnimatable;
-  scene: Scene;
-  lockObject: LockObject;
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    globalState: GlobalState;
+    animatable: IAnimatable;
+    scene: Scene;
+    lockObject: LockObject;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
-export class AnimationGridComponent extends React.Component<
-  IAnimationGridComponentProps,
-  { currentFrame: number }
-> {
-  private _animations: Nullable<Animation[]> = null;
-  private _ranges: AnimationRange[];
-  private _mainAnimatable: Nullable<Animatable>;
-  private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
-  private _isPlaying = false;
-  private timelineRef: React.RefObject<SliderLineComponent>;
-  private _isCurveEditorOpen = false;
-  private _animationControl = {
-    from: 0,
-    to: 0,
-    loop: false,
-  };
+export class AnimationGridComponent extends React.Component<IAnimationGridComponentProps, { currentFrame: number }> {
+    private _animations: Nullable<Animation[]> = null;
+    private _ranges: AnimationRange[];
+    private _mainAnimatable: Nullable<Animatable>;
+    private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
+    private _isPlaying = false;
+    private timelineRef: React.RefObject<SliderLineComponent>;
+    private _isCurveEditorOpen = false;
+    private _animationControl = {
+        from: 0,
+        to: 0,
+        loop: false,
+    };
+
+    constructor(props: IAnimationGridComponentProps) {
+        super(props);
+
+        this.state = { currentFrame: 0 };
+
+        const animatableAsAny = this.props.animatable as any;
+
+        this._ranges = animatableAsAny.getAnimationRanges ? animatableAsAny.getAnimationRanges() : [];
+        if (animatableAsAny.getAnimatables) {
+            const animatables = animatableAsAny.getAnimatables();
+            this._animations = new Array<Animation>();
+
+            animatables.forEach((animatable: IAnimatable) => {
+                if (animatable.animations) {
+                    this._animations!.push(...animatable.animations);
+                }
+            });
+
+            if (animatableAsAny.animations) {
+                this._animations!.push(...animatableAsAny.animations);
+            }
 
-  constructor(props: IAnimationGridComponentProps) {
-    super(props);
+            // Extract from and to
+            if (this._animations && this._animations.length) {
+                this._animations.forEach((animation) => {
+                    let keys = animation.getKeys();
 
-    this.state = { currentFrame: 0 };
+                    if (keys && keys.length > 0) {
+                        if (keys[0].frame < this._animationControl.from) {
+                            this._animationControl.from = keys[0].frame;
+                        }
+                        const lastKeyIndex = keys.length - 1;
+                        if (keys[lastKeyIndex].frame > this._animationControl.to) {
+                            this._animationControl.to = keys[lastKeyIndex].frame;
+                        }
+                    }
+                });
+            }
+        }
 
-    const animatableAsAny = this.props.animatable as any;
+        this.timelineRef = React.createRef();
+    }
 
-    this._ranges = animatableAsAny.getAnimationRanges
-      ? animatableAsAny.getAnimationRanges()
-      : [];
-    if (animatableAsAny.getAnimatables) {
-      const animatables = animatableAsAny.getAnimatables();
-      this._animations = new Array<Animation>();
+    playOrPause() {
+        const animatable = this.props.animatable;
+        this._isPlaying = this.props.scene.getAllAnimatablesByTarget(animatable).length > 0;
 
-      animatables.forEach((animatable: IAnimatable) => {
-        if (animatable.animations) {
-          this._animations!.push(...animatable.animations);
+        if (this._isPlaying) {
+            this.props.scene.stopAnimation(this.props.animatable);
+            this._mainAnimatable = null;
+        } else {
+            this._mainAnimatable = this.props.scene.beginAnimation(this.props.animatable, this._animationControl.from, this._animationControl.to, this._animationControl.loop);
         }
-      });
-
-      if (animatableAsAny.animations) {
-        this._animations!.push(...animatableAsAny.animations);
-      }
-
-      // Extract from and to
-      if (this._animations && this._animations.length) {
-        this._animations.forEach((animation) => {
-          let keys = animation.getKeys();
+        this.forceUpdate();
+    }
 
-          if (keys && keys.length > 0) {
-            if (keys[0].frame < this._animationControl.from) {
-              this._animationControl.from = keys[0].frame;
+    componentDidMount() {
+        this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
+            if (!this._isPlaying || !this._mainAnimatable) {
+                return;
             }
-            const lastKeyIndex = keys.length - 1;
-            if (keys[lastKeyIndex].frame > this._animationControl.to) {
-              this._animationControl.to = keys[lastKeyIndex].frame;
-            }
-          }
+            this.setState({ currentFrame: this._mainAnimatable.masterFrame });
         });
-      }
     }
 
-    this.timelineRef = React.createRef();
-  }
-
-  playOrPause() {
-    const animatable = this.props.animatable;
-    this._isPlaying =
-      this.props.scene.getAllAnimatablesByTarget(animatable).length > 0;
-
-    if (this._isPlaying) {
-      this.props.scene.stopAnimation(this.props.animatable);
-      this._mainAnimatable = null;
-    } else {
-      this._mainAnimatable = this.props.scene.beginAnimation(
-        this.props.animatable,
-        this._animationControl.from,
-        this._animationControl.to,
-        this._animationControl.loop
-      );
+    componentWillUnmount() {
+        if (this._onBeforeRenderObserver) {
+            this.props.scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
+            this._onBeforeRenderObserver = null;
+        }
     }
-    this.forceUpdate();
-  }
 
-  componentDidMount() {
-    this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(
-      () => {
-        if (!this._isPlaying || !this._mainAnimatable) {
-          return;
+    onCurrentFrameChange(value: number) {
+        if (!this._mainAnimatable) {
+            return;
         }
-        this.setState({ currentFrame: this._mainAnimatable.masterFrame });
-      }
-    );
-  }
 
-  componentWillUnmount() {
-    if (this._onBeforeRenderObserver) {
-      this.props.scene.onBeforeRenderObservable.remove(
-        this._onBeforeRenderObserver
-      );
-      this._onBeforeRenderObserver = null;
+        this._mainAnimatable.goToFrame(value);
+        this.setState({ currentFrame: value });
     }
-  }
 
-  onCurrentFrameChange(value: number) {
-    if (!this._mainAnimatable) {
-      return;
+    onChangeFromOrTo() {
+        this.playOrPause();
+        if (this._isPlaying) {
+            this.playOrPause();
+        }
     }
 
-    this._mainAnimatable.goToFrame(value);
-    this.setState({ currentFrame: value });
-  }
-
-  onChangeFromOrTo() {
-    this.playOrPause();
-    if (this._isPlaying) {
-      this.playOrPause();
+    onOpenAnimationCurveEditor() {
+        this._isCurveEditorOpen = true;
     }
-  }
-
-  onOpenAnimationCurveEditor() {
-    this._isCurveEditorOpen = true;
-  }
 
-  onCloseAnimationCurveEditor(window: Window | null) {
-    this._isCurveEditorOpen = false;
-    if (window !== null) {
-      window.close();
+    onCloseAnimationCurveEditor(window: Window | null) {
+        this._isCurveEditorOpen = false;
+        if (window !== null) {
+            window.close();
+        }
     }
-  }
-
-  render() {
-    const animatable = this.props.animatable;
-    const animatableAsAny = this.props.animatable as any;
-
-    let animatablesForTarget = this.props.scene.getAllAnimatablesByTarget(
-      animatable
-    );
-    this._isPlaying = animatablesForTarget.length > 0;
 
-    if (this._isPlaying && !this._mainAnimatable) {
-      this._mainAnimatable = animatablesForTarget[0];
-      if (this._mainAnimatable) {
-        this._animationControl.from = this._mainAnimatable.fromFrame;
-        this._animationControl.to = this._mainAnimatable.toFrame;
-        this._animationControl.loop = this._mainAnimatable.loopAnimation;
-      }
-    }
+    render() {
+        const animatable = this.props.animatable;
+        const animatableAsAny = this.props.animatable as any;
 
-    let animations = animatable.animations;
+        let animatablesForTarget = this.props.scene.getAllAnimatablesByTarget(animatable);
+        this._isPlaying = animatablesForTarget.length > 0;
 
-    return (
-      <div>
-        {this._ranges.length > 0 && (
-          <LineContainerComponent
-            globalState={this.props.globalState}
-            title='ANIMATION RANGES'
-          >
-            {this._ranges.map((range, i) => {
-              return (
-                <ButtonLineComponent
-                  key={range.name + i}
-                  label={range.name}
-                  onClick={() => {
-                    this._mainAnimatable = null;
-                    this.props.scene.beginAnimation(
-                      animatable,
-                      range.from,
-                      range.to,
-                      true
-                    );
-                  }}
-                />
-              );
-            })}
-          </LineContainerComponent>
-        )}
-        {animations && (
-          <>
-            <LineContainerComponent
-              globalState={this.props.globalState}
-              title='ANIMATIONS'
-            >
-              <TextLineComponent
-                label='Count'
-                value={animations.length.toString()}
-              />
-              <ButtonLineComponent
-                label='Edit'
-                onClick={() => this.onOpenAnimationCurveEditor()}
-              />
-              {animations.map((anim, i) => {
-                return (
-                  <TextLineComponent
-                    key={anim.targetProperty + i}
-                    label={'#' + i + ' >'}
-                    value={anim.targetProperty}
-                  />
-                );
-              })}
+        if (this._isPlaying && !this._mainAnimatable) {
+            this._mainAnimatable = animatablesForTarget[0];
+            if (this._mainAnimatable) {
+                this._animationControl.from = this._mainAnimatable.fromFrame;
+                this._animationControl.to = this._mainAnimatable.toFrame;
+                this._animationControl.loop = this._mainAnimatable.loopAnimation;
+            }
+        }
 
-              {this._isCurveEditorOpen && (
-                <PopupComponent
-                  id='curve-editor'
-                  title='Curve Animation Editor'
-                  size={{ width: 1024, height: 490 }}
-                  onOpen={(window: Window) => {}}
-                  onClose={(window: Window) =>
-                    this.onCloseAnimationCurveEditor(window)
-                  }
-                >
-                  <AnimationCurveEditorComponent
-                    scene={this.props.scene}
-                    entity={animatableAsAny}
-                    close={(event) =>
-                      this.onCloseAnimationCurveEditor(event.view)
-                    }
-                    lockObject={this.props.lockObject}
-                    playOrPause={() => this.playOrPause()}
-                    globalState={this.props.globalState}
-                  />
-                </PopupComponent>
-              )}
-            </LineContainerComponent>
-            {animations.length > 0 && (
-              <LineContainerComponent
-                globalState={this.props.globalState}
-                title='ANIMATION GENERAL CONTROL'
-              >
-                <FloatLineComponent
-                  lockObject={this.props.lockObject}
-                  isInteger={true}
-                  label='From'
-                  target={this._animationControl}
-                  propertyName='from'
-                  onChange={() => this.onChangeFromOrTo()}
-                />
-                <FloatLineComponent
-                  lockObject={this.props.lockObject}
-                  isInteger={true}
-                  label='To'
-                  target={this._animationControl}
-                  propertyName='to'
-                  onChange={() => this.onChangeFromOrTo()}
-                />
-                <CheckBoxLineComponent
-                  label='Loop'
-                  onSelect={(value) => (this._animationControl.loop = value)}
-                  isSelected={() => this._animationControl.loop}
-                />
-                {this._isPlaying && (
-                  <SliderLineComponent
-                    ref={this.timelineRef}
-                    label='Current frame'
-                    minimum={this._animationControl.from}
-                    maximum={this._animationControl.to}
-                    step={
-                      (this._animationControl.to -
-                        this._animationControl.from) /
-                      1000.0
-                    }
-                    directValue={this.state.currentFrame}
-                    onInput={(value) => this.onCurrentFrameChange(value)}
-                  />
+        let animations = animatable.animations;
+
+        return (
+            <div>
+                {this._ranges.length > 0 && (
+                    <LineContainerComponent globalState={this.props.globalState} title="ANIMATION RANGES">
+                        {this._ranges.map((range, i) => {
+                            return (
+                                <ButtonLineComponent
+                                    key={range.name + i}
+                                    label={range.name}
+                                    onClick={() => {
+                                        this._mainAnimatable = null;
+                                        this.props.scene.beginAnimation(animatable, range.from, range.to, true);
+                                    }}
+                                />
+                            );
+                        })}
+                    </LineContainerComponent>
                 )}
-                <ButtonLineComponent
-                  label={this._isPlaying ? 'Stop' : 'Play'}
-                  onClick={() => this.playOrPause()}
-                />
-                {(this._ranges.length > 0 ||
-                  (this._animations && this._animations.length > 0)) && (
-                  <>
-                    <CheckBoxLineComponent
-                      label='Enable override'
-                      onSelect={(value) => {
-                        if (value) {
-                          animatableAsAny.animationPropertiesOverride = new AnimationPropertiesOverride();
-                          animatableAsAny.animationPropertiesOverride.blendingSpeed = 0.05;
-                        } else {
-                          animatableAsAny.animationPropertiesOverride = null;
-                        }
-                        this.forceUpdate();
-                      }}
-                      isSelected={() =>
-                        animatableAsAny.animationPropertiesOverride != null
-                      }
-                      onValueChanged={() => this.forceUpdate()}
-                    />
-                    {animatableAsAny.animationPropertiesOverride != null && (
-                      <div>
-                        <CheckBoxLineComponent
-                          label='Enable blending'
-                          target={animatableAsAny.animationPropertiesOverride}
-                          propertyName='enableBlending'
-                          onPropertyChangedObservable={
-                            this.props.onPropertyChangedObservable
-                          }
-                        />
-                        <SliderLineComponent
-                          label='Blending speed'
-                          target={animatableAsAny.animationPropertiesOverride}
-                          propertyName='blendingSpeed'
-                          minimum={0}
-                          maximum={0.1}
-                          step={0.01}
-                          onPropertyChangedObservable={
-                            this.props.onPropertyChangedObservable
-                          }
-                        />
-                      </div>
-                    )}
-                  </>
+                {animations && (
+                    <>
+                        <LineContainerComponent globalState={this.props.globalState} title="ANIMATIONS">
+                            <TextLineComponent label="Count" value={animations.length.toString()} />
+                            <ButtonLineComponent label="Edit" onClick={() => this.onOpenAnimationCurveEditor()} />
+                            {animations.map((anim, i) => {
+                                return <TextLineComponent key={anim.targetProperty + i} label={"#" + i + " >"} value={anim.targetProperty} />;
+                            })}
+
+                            {this._isCurveEditorOpen && (
+                                <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
+                                    <AnimationCurveEditorComponent scene={this.props.scene} entity={animatableAsAny} lockObject={this.props.lockObject} playOrPause={() => this.playOrPause()} globalState={this.props.globalState} />
+                                </PopupComponent>
+                            )}
+                        </LineContainerComponent>
+                        {animations.length > 0 && (
+                            <LineContainerComponent globalState={this.props.globalState} title="ANIMATION GENERAL CONTROL">
+                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="From" target={this._animationControl} propertyName="from" onChange={() => this.onChangeFromOrTo()} />
+                                <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="To" target={this._animationControl} propertyName="to" onChange={() => this.onChangeFromOrTo()} />
+                                <CheckBoxLineComponent label="Loop" onSelect={(value) => (this._animationControl.loop = value)} isSelected={() => this._animationControl.loop} />
+                                {this._isPlaying && (
+                                    <SliderLineComponent
+                                        ref={this.timelineRef}
+                                        label="Current frame"
+                                        minimum={this._animationControl.from}
+                                        maximum={this._animationControl.to}
+                                        step={(this._animationControl.to - this._animationControl.from) / 1000.0}
+                                        directValue={this.state.currentFrame}
+                                        onInput={(value) => this.onCurrentFrameChange(value)}
+                                    />
+                                )}
+                                <ButtonLineComponent label={this._isPlaying ? "Stop" : "Play"} onClick={() => this.playOrPause()} />
+                                {(this._ranges.length > 0 || (this._animations && this._animations.length > 0)) && (
+                                    <>
+                                        <CheckBoxLineComponent
+                                            label="Enable override"
+                                            onSelect={(value) => {
+                                                if (value) {
+                                                    animatableAsAny.animationPropertiesOverride = new AnimationPropertiesOverride();
+                                                    animatableAsAny.animationPropertiesOverride.blendingSpeed = 0.05;
+                                                } else {
+                                                    animatableAsAny.animationPropertiesOverride = null;
+                                                }
+                                                this.forceUpdate();
+                                            }}
+                                            isSelected={() => animatableAsAny.animationPropertiesOverride != null}
+                                            onValueChanged={() => this.forceUpdate()}
+                                        />
+                                        {animatableAsAny.animationPropertiesOverride != null && (
+                                            <div>
+                                                <CheckBoxLineComponent label="Enable blending" target={animatableAsAny.animationPropertiesOverride} propertyName="enableBlending" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                                                <SliderLineComponent label="Blending speed" target={animatableAsAny.animationPropertiesOverride} propertyName="blendingSpeed" minimum={0} maximum={0.1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                                            </div>
+                                        )}
+                                    </>
+                                )}
+                            </LineContainerComponent>
+                        )}
+                    </>
                 )}
-              </LineContainerComponent>
-            )}
-          </>
-        )}
-      </div>
-    );
-  }
+            </div>
+        );
+    }
 }

+ 78 - 35
inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx

@@ -7,12 +7,15 @@ interface IControlsProps {
     selected: IAnimationKey | null;
     currentFrame: number;
     onCurrentFrameChange: (frame: number) => void;
-    repositionCanvas: (frame: number) => void;
+    repositionCanvas: (keyframe: IAnimationKey) => void;
     playPause: (direction: number) => void;
     isPlaying: boolean;
     scrollable: React.RefObject<HTMLDivElement>;
 }
 
+/**
+ * The playback controls for the animation editor
+ */
 export class Controls extends React.Component<IControlsProps, { selected: IAnimationKey; playingType: string }> {
     readonly _sizeOfKeyframe: number = 5;
     constructor(props: IControlsProps) {
@@ -22,91 +25,131 @@ export class Controls extends React.Component<IControlsProps, { selected: IAnima
         }
     }
 
-    playBackwards() {
+    playBackwards = () => {
         this.setState({ playingType: "reverse" });
         this.props.playPause(-1);
-    }
+    };
 
-    play() {
+    play = () => {
         this.setState({ playingType: "forward" });
         this.props.playPause(1);
-    }
+    };
 
-    pause() {
+    pause = () => {
         if (this.props.isPlaying) {
             this.setState({ playingType: "" });
             this.props.playPause(0);
         }
-    }
+    };
 
-    moveToAnimationStart() {
-        const start = this.props.keyframes && this.props.keyframes[0].frame;
-        if (start !== undefined && typeof start === "number") {
-            this.props.onCurrentFrameChange(start);
-            this.props.repositionCanvas(start);
+    moveToAnimationStart = () => {
+        const startKeyframe = this.props.keyframes && this.props.keyframes[0];
+        if (startKeyframe !== null) {
+            if (typeof startKeyframe.frame === "number") {
+                this.props.onCurrentFrameChange(startKeyframe.frame);
+            }
         }
-    }
+    };
 
-    moveToAnimationEnd() {
-        const end = this.props.keyframes && this.props.keyframes[this.props.keyframes.length - 1].frame;
-        if (end !== undefined && typeof end === "number") {
-            this.props.onCurrentFrameChange(end);
-            this.props.repositionCanvas(end);
+    moveToAnimationEnd = () => {
+        const endKeyframe = this.props.keyframes && this.props.keyframes[this.props.keyframes.length - 1];
+        if (endKeyframe !== null) {
+            if (typeof endKeyframe.frame === "number") {
+                this.props.onCurrentFrameChange(endKeyframe.frame);
+            }
         }
-    }
+    };
 
-    nextKeyframe() {
+    nextKeyframe = () => {
         if (this.props.keyframes !== null) {
             let first = this.props.keyframes.find((kf) => kf.frame > this.props.currentFrame);
             if (first) {
                 this.props.onCurrentFrameChange(first.frame);
-                this.props.repositionCanvas(first.frame);
                 this.setState({ selected: first });
                 (this.props.scrollable.current as HTMLDivElement).scrollLeft = first.frame * this._sizeOfKeyframe;
             }
         }
-    }
+    };
 
-    previousKeyframe() {
+    previousKeyframe = () => {
         if (this.props.keyframes !== null) {
             let keyframes = [...this.props.keyframes];
             let first = keyframes.reverse().find((kf) => kf.frame < this.props.currentFrame);
             if (first) {
                 this.props.onCurrentFrameChange(first.frame);
-                this.props.repositionCanvas(first.frame);
                 this.setState({ selected: first });
                 (this.props.scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * this._sizeOfKeyframe);
             }
         }
-    }
+    };
 
     render() {
         return (
             <div className="controls">
-                <IconButtonLineComponent tooltip="Animation Start" icon="animation-start" onClick={() => this.moveToAnimationStart()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Previous Keyframe" icon="animation-lastkey" onClick={() => this.previousKeyframe()}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation Start"
+                    icon="animation-start"
+                    onClick={this.moveToAnimationStart}
+                ></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Previous Keyframe"
+                    icon="animation-lastkey"
+                    onClick={this.previousKeyframe}
+                ></IconButtonLineComponent>
                 {this.props.isPlaying ? (
                     <div className="stop-container">
                         {this.state.playingType === "reverse" ? (
                             <>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={() => this.pause()}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={() => this.play()}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}
+                                ></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Forward"
+                                    icon="animation-playfwd"
+                                    onClick={this.play}
+                                ></IconButtonLineComponent>
                             </>
                         ) : (
                             <>
-                                <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={() => this.playBackwards()}></IconButtonLineComponent>
-                                <IconButtonLineComponent tooltip="Pause" icon="animation-stop" onClick={() => this.pause()}></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Play Reverse"
+                                    icon="animation-playrev"
+                                    onClick={this.playBackwards}
+                                ></IconButtonLineComponent>
+                                <IconButtonLineComponent
+                                    tooltip="Pause"
+                                    icon="animation-stop"
+                                    onClick={this.pause}
+                                ></IconButtonLineComponent>
                             </>
                         )}
                     </div>
                 ) : (
                     <div className="stop-container">
-                        <IconButtonLineComponent tooltip="Play Reverse" icon="animation-playrev" onClick={() => this.playBackwards()}></IconButtonLineComponent>
-                        <IconButtonLineComponent tooltip="Play Forward" icon="animation-playfwd" onClick={() => this.play()}></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Reverse"
+                            icon="animation-playrev"
+                            onClick={this.playBackwards}
+                        ></IconButtonLineComponent>
+                        <IconButtonLineComponent
+                            tooltip="Play Forward"
+                            icon="animation-playfwd"
+                            onClick={this.play}
+                        ></IconButtonLineComponent>
                     </div>
                 )}
-                <IconButtonLineComponent tooltip="Next Keyframe" icon="animation-nextkey" onClick={() => this.nextKeyframe()}></IconButtonLineComponent>
-                <IconButtonLineComponent tooltip="Animation End" icon="animation-end" onClick={() => this.moveToAnimationEnd()}></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Next Keyframe"
+                    icon="animation-nextkey"
+                    onClick={this.nextKeyframe}
+                ></IconButtonLineComponent>
+                <IconButtonLineComponent
+                    tooltip="Animation End"
+                    icon="animation-end"
+                    onClick={this.moveToAnimationEnd}
+                ></IconButtonLineComponent>
             </div>
         );
     }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1204 - 1154
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss


+ 294 - 272
inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx

@@ -1,299 +1,321 @@
-import * as React from 'react';
-
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
-import { Animation } from 'babylonjs/Animations/animation';
-import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
-import { NumericInputComponent } from '../../../lines/numericInputComponent';
-import { AddAnimation } from './addAnimation';
-import { AnimationListTree, SelectedCoordinate } from './animationListTree';
-import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
-import { TargetedAnimation } from 'babylonjs/Animations/animationGroup';
-import { LoadSnippet } from './loadsnippet';
-import { SaveSnippet } from './saveSnippet';
-import { LockObject } from '../lockObject';
-import { GlobalState } from '../../../../globalState';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { Animation } from "babylonjs/Animations/animation";
+import { IconButtonLineComponent } from "../../../lines/iconButtonLineComponent";
+import { NumericInputComponent } from "../../../lines/numericInputComponent";
+import { AddAnimation } from "./addAnimation";
+import { AnimationListTree, SelectedCoordinate } from "./animationListTree";
+import { IAnimatable } from "babylonjs/Animations/animatable.interface";
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+import { LoadSnippet } from "./loadsnippet";
+import { SaveSnippet } from "./saveSnippet";
+import { LockObject } from "../lockObject";
+import { GlobalState } from "../../../../globalState";
 
 interface IEditorControlsProps {
-  isTargetedAnimation: boolean;
-  entity: IAnimatable | TargetedAnimation;
-  selected: Animation | null;
-  lockObject: LockObject;
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
-  setNotificationMessage: (message: string) => void;
-  selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
-  setFps: (fps: number) => void;
-  setIsLooping: () => void;
-  globalState: GlobalState;
-  snippetServer: string;
-  deselectAnimation: () => void;
-  fps: number;
+    isTargetedAnimation: boolean;
+    entity: IAnimatable | TargetedAnimation;
+    selected: Animation | null;
+    lockObject: LockObject;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    setNotificationMessage: (message: string) => void;
+    selectAnimation: (selected: Animation, axis?: SelectedCoordinate) => void;
+    setFps: (fps: number) => void;
+    setIsLooping: () => void;
+    globalState: GlobalState;
+    snippetServer: string;
+    deselectAnimation: () => void;
+    fps: number;
 }
 
+/**
+ * Renders the Curve Editor controls to create, save, remove, load and edit animations
+ */
 export class EditorControls extends React.Component<
-  IEditorControlsProps,
-  {
-    isAnimationTabOpen: boolean;
-    isEditTabOpen: boolean;
-    isLoadTabOpen: boolean;
-    isSaveTabOpen: boolean;
-    isLoopActive: boolean;
-    animationsCount: number;
-    framesPerSecond: number;
-    snippetId: string;
-    selected: Animation | undefined;
-  }
+    IEditorControlsProps,
+    {
+        isAnimationTabOpen: boolean;
+        isEditTabOpen: boolean;
+        isLoadTabOpen: boolean;
+        isSaveTabOpen: boolean;
+        isLoopActive: boolean;
+        animationsCount: number;
+        framesPerSecond: number;
+        snippetId: string;
+        selected: Animation | undefined;
+    }
 > {
-  constructor(props: IEditorControlsProps) {
-    super(props);
-    let count = this.props.isTargetedAnimation
-      ? 1
-      : (this.props.entity as IAnimatable).animations?.length ?? 0;
-    this.state = {
-      isAnimationTabOpen: count === 0 ? true : false,
-      isEditTabOpen: count === 0 ? false : true,
-      isSaveTabOpen: false,
-      isLoadTabOpen: false,
-      isLoopActive: true,
-      animationsCount: count,
-      framesPerSecond: this.props.fps,
-      snippetId: '',
-      selected: undefined,
-    };
-  }
+    constructor(props: IEditorControlsProps) {
+        super(props);
+        let count = this.props.isTargetedAnimation ? 1 : (this.props.entity as IAnimatable).animations?.length ?? 0;
+        this.state = {
+            isAnimationTabOpen: count === 0 ? true : false,
+            isEditTabOpen: count === 0 ? false : true,
+            isSaveTabOpen: false,
+            isLoadTabOpen: false,
+            isLoopActive: true,
+            animationsCount: count,
+            framesPerSecond: this.props.fps,
+            snippetId: "",
+            selected: undefined,
+        };
+    }
 
-  componentWillReceiveProps(newProps: IEditorControlsProps) {
-    if (newProps.fps !== this.props.fps) {
-      this.setState({ framesPerSecond: newProps.fps });
+    componentDidUpdate(prevProps: IEditorControlsProps) {
+        if (this.props.fps !== prevProps.fps) {
+            this.setState({ framesPerSecond: this.props.fps });
+        }
     }
-  }
 
-  animationAdded() {
-    this.setState({
-      animationsCount: this.recountAnimations(),
-      isEditTabOpen: true,
-      isAnimationTabOpen: false,
-    });
-  }
+    onAnimationAdded = (animation: Animation) => {
+        this.setState({
+            animationsCount: this.recountAnimations(),
+            isEditTabOpen: true,
+            isAnimationTabOpen: false,
+        });
+        this.props.selectAnimation(animation, undefined);
+    };
 
-  finishedUpdate() {
-    this.setState({
-      isEditTabOpen: true,
-      isAnimationTabOpen: false,
-      selected: undefined,
-    });
-  }
+    finishedUpdate = () => {
+        this.setState({
+            isEditTabOpen: true,
+            isAnimationTabOpen: false,
+            selected: undefined,
+        });
+    };
 
-  recountAnimations() {
-    return (this.props.entity as IAnimatable).animations?.length ?? 0;
-  }
+    recountAnimations() {
+        return (this.props.entity as IAnimatable).animations?.length ?? 0;
+    }
 
-  changeLoopBehavior() {
-    this.setState({
-      isLoopActive: !this.state.isLoopActive,
-    });
-    this.props.setIsLooping();
-  }
+    changeLoopBehavior = () => {
+        this.setState({
+            isLoopActive: !this.state.isLoopActive,
+        });
+        this.props.setIsLooping();
+    };
 
-  handleTabs(tab: number) {
-    let state = {
-      isAnimationTabOpen: true,
-      isLoadTabOpen: false,
-      isSaveTabOpen: false,
-      isEditTabOpen: false,
+    handleFirstTab = () => {
+        this.handleTabs(0);
+    };
+    handleSecondTab = () => {
+        this.handleTabs(1);
+    };
+    handleThirdTab = () => {
+        this.handleTabs(2);
+    };
+    handleFourthTab = () => {
+        this.handleTabs(3);
     };
 
-    switch (tab) {
-      case 0:
-        state = {
-          isAnimationTabOpen: true,
-          isLoadTabOpen: false,
-          isSaveTabOpen: false,
-          isEditTabOpen: false,
+    handleTabs(tab: number) {
+        let state = {
+            isAnimationTabOpen: true,
+            isLoadTabOpen: false,
+            isSaveTabOpen: false,
+            isEditTabOpen: false,
         };
-        break;
-      case 1:
-        state = {
-          isAnimationTabOpen: false,
-          isLoadTabOpen: true,
-          isSaveTabOpen: false,
-          isEditTabOpen: false,
-        };
-        break;
-      case 2:
-        state = {
-          isAnimationTabOpen: false,
-          isLoadTabOpen: false,
-          isSaveTabOpen: true,
-          isEditTabOpen: false,
-        };
-        break;
-      case 3:
-        state = {
-          isAnimationTabOpen: false,
-          isLoadTabOpen: false,
-          isSaveTabOpen: false,
-          isEditTabOpen: true,
-        };
-        break;
-    }
 
-    this.setState(state);
-  }
+        switch (tab) {
+            case 0:
+                state = {
+                    isAnimationTabOpen: true,
+                    isLoadTabOpen: false,
+                    isSaveTabOpen: false,
+                    isEditTabOpen: false,
+                };
+                break;
+            case 1:
+                state = {
+                    isAnimationTabOpen: false,
+                    isLoadTabOpen: true,
+                    isSaveTabOpen: false,
+                    isEditTabOpen: false,
+                };
+                break;
+            case 2:
+                state = {
+                    isAnimationTabOpen: false,
+                    isLoadTabOpen: false,
+                    isSaveTabOpen: true,
+                    isEditTabOpen: false,
+                };
+                break;
+            case 3:
+                state = {
+                    isAnimationTabOpen: false,
+                    isLoadTabOpen: false,
+                    isSaveTabOpen: false,
+                    isEditTabOpen: true,
+                };
+                break;
+        }
 
-  handleChangeFps(fps: number) {
-    this.props.setFps(fps);
-    this.setState({ framesPerSecond: fps });
-    if (this.props.selected) {
-      this.props.selected.framePerSecond = fps;
+        this.setState(state);
     }
-  }
 
-  emptiedList() {
-    this.setState({
-      animationsCount: this.recountAnimations(),
-      isEditTabOpen: false,
-      isAnimationTabOpen: true,
-    });
-  }
-
-  animationsLoaded(numberOfAnimations: number) {
-    this.setState({
-      animationsCount: numberOfAnimations,
-      isEditTabOpen: true,
-      isAnimationTabOpen: false,
-      isLoadTabOpen: false,
-      isSaveTabOpen: false,
-    });
-  }
+    handleChangeFps = (fps: number) => {
+        this.props.setFps(fps);
+        this.setState({ framesPerSecond: fps });
+        if (this.props.selected) {
+            this.props.selected.framePerSecond = fps;
+        }
+    };
 
-  editAnimation(selected: Animation) {
-    this.setState({
-      selected: selected,
-      isEditTabOpen: false,
-      isAnimationTabOpen: true,
-      isLoadTabOpen: false,
-      isSaveTabOpen: false,
-    });
-  }
+    /**
+     * Cleans the list when has been emptied
+     */
+    onEmptiedList = () => {
+        this.setState({
+            animationsCount: this.recountAnimations(),
+            isEditTabOpen: false,
+            isAnimationTabOpen: true,
+        });
+    };
 
-  render() {
-    return (
-      <div className='animation-list'>
-        <div className='controls-header'>
-          {this.props.isTargetedAnimation ? null : (
-            <IconButtonLineComponent
-              active={this.state.isAnimationTabOpen}
-              tooltip='Add Animation'
-              icon='medium add-animation'
-              onClick={() => this.handleTabs(0)}
-            ></IconButtonLineComponent>
-          )}
-          <IconButtonLineComponent
-            active={this.state.isLoadTabOpen}
-            tooltip='Load Animation'
-            icon='medium load'
-            onClick={() => this.handleTabs(1)}
-          ></IconButtonLineComponent>
-          {this.state.animationsCount === 0 ? null : (
-            <IconButtonLineComponent
-              active={this.state.isSaveTabOpen}
-              tooltip='Save Animation'
-              icon='medium save'
-              onClick={() => this.handleTabs(2)}
-            ></IconButtonLineComponent>
-          )}
-          {this.state.animationsCount === 0 ? null : (
-            <IconButtonLineComponent
-              active={this.state.isEditTabOpen}
-              tooltip='Edit Animations'
-              icon='medium animation-edit'
-              onClick={() => this.handleTabs(3)}
-            ></IconButtonLineComponent>
-          )}
-          {this.state.isEditTabOpen ? (
-            <div className='input-fps'>
-              <NumericInputComponent
-                label={''}
-                precision={0}
-                value={this.state.framesPerSecond}
-                onChange={(framesPerSecond: number) =>
-                  this.handleChangeFps(framesPerSecond)
-                }
-              />
-              <p>fps</p>
-            </div>
-          ) : null}
-          {this.state.isEditTabOpen ? (
-            <IconButtonLineComponent
-              tooltip='Loop/Unloop'
-              icon={`medium ${
-                this.state.isLoopActive
-                  ? 'loop-active last'
-                  : 'loop-inactive last'
-              }`}
-              onClick={() => this.changeLoopBehavior()}
-            ></IconButtonLineComponent>
-          ) : null}
-        </div>
-        {this.props.isTargetedAnimation ? null : (
-          <AddAnimation
-            isOpen={this.state.isAnimationTabOpen}
-            close={() => {
-              this.setState({ isAnimationTabOpen: false, isEditTabOpen: true });
-            }}
-            entity={this.props.entity as IAnimatable}
-            setNotificationMessage={(message: string) => {
-              this.props.setNotificationMessage(message);
-            }}
-            addedNewAnimation={() => this.animationAdded()}
-            onPropertyChangedObservable={this.props.onPropertyChangedObservable}
-            fps={this.state.framesPerSecond}
-            selectedToUpdate={this.state.selected}
-            finishedUpdate={() => this.finishedUpdate()}
-          />
-        )}
+    /**
+     * When animations have been reloaded update tabs
+     */
+    animationsLoaded = (numberOfAnimations: number) => {
+        this.setState({
+            animationsCount: numberOfAnimations,
+            isEditTabOpen: true,
+            isAnimationTabOpen: false,
+            isLoadTabOpen: false,
+            isSaveTabOpen: false,
+        });
 
-        {this.state.isLoadTabOpen ? (
-          <LoadSnippet
-            animationsLoaded={(numberOfAnimations: number) =>
-              this.animationsLoaded(numberOfAnimations)
+        if (this.props.entity instanceof TargetedAnimation) {
+            const animation = (this.props.entity as TargetedAnimation).animation;
+            this.props.selectAnimation(animation);
+        } else {
+            const animations = (this.props.entity as IAnimatable).animations;
+            if (animations !== null) {
+                this.props.selectAnimation(animations[0]);
             }
-            lockObject={this.props.lockObject}
-            animations={[]}
-            snippetServer={this.props.snippetServer}
-            globalState={this.props.globalState}
-            setSnippetId={(id: string) => this.setState({ snippetId: id })}
-            entity={this.props.entity}
-            setNotificationMessage={this.props.setNotificationMessage}
-          />
-        ) : null}
+        }
+    };
 
-        {this.state.isSaveTabOpen ? (
-          <SaveSnippet
-            lockObject={this.props.lockObject}
-            animations={(this.props.entity as IAnimatable).animations}
-            snippetServer={this.props.snippetServer}
-            globalState={this.props.globalState}
-            snippetId={this.state.snippetId}
-          />
-        ) : null}
+    editAnimation = (selected: Animation) => {
+        this.setState({
+            selected: selected,
+            isEditTabOpen: false,
+            isAnimationTabOpen: true,
+            isLoadTabOpen: false,
+            isSaveTabOpen: false,
+        });
+    };
 
-        {this.state.isEditTabOpen ? (
-          <AnimationListTree
-            deselectAnimation={() => this.props.deselectAnimation()}
-            isTargetedAnimation={this.props.isTargetedAnimation}
-            entity={this.props.entity}
-            selected={this.props.selected}
-            onPropertyChangedObservable={this.props.onPropertyChangedObservable}
-            empty={() => this.emptiedList()}
-            selectAnimation={this.props.selectAnimation}
-            editAnimation={(selected: Animation) =>
-              this.editAnimation(selected)
-            }
-          />
-        ) : null}
-      </div>
-    );
-  }
+    setSnippetId = (id: string) => {
+        this.setState({ snippetId: id });
+    };
+
+     /**
+     * Marks animation tab closed and hides the tab
+     */
+    onCloseAddAnimation = () => {
+        this.setState({ isAnimationTabOpen: false, isEditTabOpen: true });
+    };
+
+    render() {
+        return (
+            <div className="animation-list">
+                <div className="controls-header">
+                    {this.props.isTargetedAnimation ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isAnimationTabOpen}
+                            tooltip="Add Animation"
+                            icon="medium add-animation"
+                            onClick={this.handleFirstTab}></IconButtonLineComponent>
+                    )}
+                    <IconButtonLineComponent
+                        active={this.state.isLoadTabOpen}
+                        tooltip="Load Animation"
+                        icon="medium load"
+                        onClick={this.handleSecondTab}></IconButtonLineComponent>
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isSaveTabOpen}
+                            tooltip="Save Animation"
+                            icon="medium save"
+                            onClick={this.handleThirdTab}></IconButtonLineComponent>
+                    )}
+                    {this.state.animationsCount === 0 ? null : (
+                        <IconButtonLineComponent
+                            active={this.state.isEditTabOpen}
+                            tooltip="Edit Animations"
+                            icon="medium animation-edit"
+                            onClick={this.handleFourthTab}></IconButtonLineComponent>
+                    )}
+                    {this.state.isEditTabOpen ? (
+                        <div className="input-fps">
+                            <NumericInputComponent
+                                label={""}
+                                precision={0}
+                                value={this.state.framesPerSecond}
+                                onChange={this.handleChangeFps}
+                            />
+                            <p>fps</p>
+                        </div>
+                    ) : null}
+                    {this.state.isEditTabOpen ? (
+                        <IconButtonLineComponent
+                            tooltip="Loop/Unloop"
+                            icon={`medium ${this.state.isLoopActive ? "loop-active last" : "loop-inactive last"}`}
+                            onClick={this.changeLoopBehavior}></IconButtonLineComponent>
+                    ) : null}
+                </div>
+                {this.props.isTargetedAnimation ? null : (
+                    <AddAnimation
+                        isOpen={this.state.isAnimationTabOpen}
+                        close={this.onCloseAddAnimation}
+                        entity={this.props.entity as IAnimatable}
+                        setNotificationMessage={this.props.setNotificationMessage}
+                        addedNewAnimation={this.onAnimationAdded}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                        fps={this.state.framesPerSecond}
+                        selectedToUpdate={this.state.selected}
+                        finishedUpdate={this.finishedUpdate}
+                    />
+                )}
+
+                {this.state.isLoadTabOpen ? (
+                    <LoadSnippet
+                        animationsLoaded={this.animationsLoaded}
+                        lockObject={this.props.lockObject}
+                        animations={[]}
+                        snippetServer={this.props.snippetServer}
+                        globalState={this.props.globalState}
+                        setSnippetId={this.setSnippetId}
+                        entity={this.props.entity}
+                        setNotificationMessage={this.props.setNotificationMessage}
+                    />
+                ) : null}
+
+                {this.state.isSaveTabOpen ? (
+                    <SaveSnippet
+                        lockObject={this.props.lockObject}
+                        animations={(this.props.entity as IAnimatable).animations}
+                        snippetServer={this.props.snippetServer}
+                        globalState={this.props.globalState}
+                        snippetId={this.state.snippetId}
+                    />
+                ) : null}
+
+                {this.state.isEditTabOpen ? (
+                    <AnimationListTree
+                        deselectAnimation={this.props.deselectAnimation}
+                        isTargetedAnimation={this.props.isTargetedAnimation}
+                        entity={this.props.entity}
+                        selected={this.props.selected}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable}
+                        empty={this.onEmptiedList}
+                        selectAnimation={this.props.selectAnimation}
+                        editAnimation={this.editAnimation}
+                    />
+                ) : null}
+            </div>
+        );
+    }
 }

+ 174 - 124
inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx

@@ -1,132 +1,182 @@
-import * as React from 'react';
-import { IconButtonLineComponent } from '../../../lines/iconButtonLineComponent';
-import { IActionableKeyFrame } from './animationCurveEditorComponent';
+import * as React from "react";
+import { IconButtonLineComponent } from "../../../lines/iconButtonLineComponent";
+import { IActionableKeyFrame } from "./animationCurveEditorComponent";
 
 interface IGraphActionsBarProps {
-  addKeyframe: () => void;
-  removeKeyframe: () => void;
-  handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
-  handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
-  flatTangent: () => void;
-  brokeTangents: () => void;
-  setLerpMode: () => void;
-  brokenMode: boolean;
-  lerpMode: boolean;
-  actionableKeyframe: IActionableKeyFrame;
-  title: string;
-  close: (event: any) => void;
-  enabled: boolean;
-  setKeyframeValue: () => void;
+    addKeyframe: () => void;
+    removeKeyframe: () => void;
+    frameSelectedKeyframes: () => void;
+    handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+    handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+    flatTangent: () => void;
+    brokeTangents: () => void;
+    setLerpToActiveControlPoint: () => void;
+    brokenMode: boolean;
+    lerpMode: boolean;
+    actionableKeyframe: IActionableKeyFrame;
+    title: string;
+    enabled: boolean;
+    setKeyframeValue: (actionableKeyframe: IActionableKeyFrame) => void;
+    frameRange: { min: number | undefined; max: number | undefined };
 }
 
-export class GraphActionsBar extends React.Component<IGraphActionsBarProps> {
-  private _frameInput: React.RefObject<HTMLInputElement>;
-  private _valueInput: React.RefObject<HTMLInputElement>;
-  constructor(props: IGraphActionsBarProps) {
-    super(props);
-    this._frameInput = React.createRef();
-    this._valueInput = React.createRef();
-  }
-
-  componentDidMount() {
-    this._frameInput.current?.addEventListener(
-      'keyup',
-      this.isEnterKeyUp.bind(this)
-    );
-    this._valueInput.current?.addEventListener(
-      'keyup',
-      this.isEnterKeyUp.bind(this)
-    );
-  }
-
-  componentWillUnmount() {
-    this._frameInput.current?.removeEventListener(
-      'keyup',
-      this.isEnterKeyUp.bind(this)
-    );
-    this._valueInput.current?.removeEventListener(
-      'keyup',
-      this.isEnterKeyUp.bind(this)
-    );
-  }
-
-  isEnterKeyUp(event: KeyboardEvent) {
-    event.preventDefault();
-
-    if (event.key === 'Enter') {
-      this.props.setKeyframeValue();
+/**
+ * Has the buttons and actions for the Canvas Graph.
+ * Handles input change and actions (flat, broken mode, set linear control points)
+ */
+export class GraphActionsBar extends React.Component<
+    IGraphActionsBarProps,
+    { frame: string; value: string; min: number | undefined; max: number | undefined }
+> {
+    private _frameInput: React.RefObject<HTMLInputElement>;
+    private _valueInput: React.RefObject<HTMLInputElement>;
+    constructor(props: IGraphActionsBarProps) {
+        super(props);
+        this._frameInput = React.createRef();
+        this._valueInput = React.createRef();
+        const { frame, value } = this.selectedKeyframeChanged(this.props.actionableKeyframe);
+        this.state = { frame, value, min: this.props.frameRange.min, max: this.props.frameRange.max };
     }
-  }
 
-  onBlur(event: React.FocusEvent<HTMLInputElement>) {
-    event.preventDefault();
-    if (event.target.value !== '') {
-      this.props.setKeyframeValue();
+    componentDidMount() {
+        this._frameInput.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
+        this._valueInput.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
+    }
+
+    componentDidUpdate(prevProps: IGraphActionsBarProps, prevState: any) {
+        if (prevProps.actionableKeyframe !== this.props.actionableKeyframe) {
+            const { frame, value } = this.selectedKeyframeChanged(this.props.actionableKeyframe);
+            this.setState({ frame, value });
+        }
+
+        if (
+            prevProps.frameRange.min !== this.props.frameRange.min ||
+            prevProps.frameRange.max !== this.props.frameRange.max
+        ) {
+            this.setState({ min: this.props.frameRange.min, max: this.props.frameRange.max });
+        }
+    }
+
+    selectedKeyframeChanged(keyframe: IActionableKeyFrame) {
+        let frame = "";
+        if (typeof keyframe.frame === "number") {
+            frame = keyframe.frame.toString();
+        }
+        let value = "";
+        if (typeof keyframe.value === "number") {
+            value = keyframe.value.toFixed(3);
+        }
+        return { frame, value };
+    }
+
+    componentWillUnmount() {
+        this._frameInput.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
+        this._valueInput.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
+    }
+
+    isEnterKeyUp(event: KeyboardEvent) {
+        event.preventDefault();
+
+        if (event.key === "Enter") {
+            const actionableKeyframe: IActionableKeyFrame = { frame: this.getFrame(), value: this.getValue() };
+            this.props.setKeyframeValue(actionableKeyframe);
+        }
+    }
+
+    onBlur = (event: React.FocusEvent<HTMLInputElement>) => {
+        event.preventDefault();
+        if (event.target.value !== "") {
+            const actionableKeyframe: IActionableKeyFrame = { frame: this.getFrame(), value: this.getValue() };
+            this.props.setKeyframeValue(actionableKeyframe);
+        }
+    };
+
+    getFrame() {
+        let frame;
+        if (this.state.frame === "") {
+            frame = "";
+        } else {
+            frame = parseInt(this.state.frame);
+        }
+
+        return frame;
+    }
+
+    getValue() {
+        let value;
+        if (this.state.value !== "") {
+            value = parseFloat(this.state.value);
+        } else {
+            value = "";
+        }
+        return value;
+    }
+
+    handleValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        e.preventDefault();
+        this.setState({ value: e.target.value });
+    };
+
+    handleFrameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        e.preventDefault();
+        this.setState({ frame: e.target.value });
+    };
+
+    render() {
+        return (
+            <div className="actions-wrapper">
+                <div className="title-container">
+                    <div className="icon babylon-logo"></div>
+                    <div className="title">{this.props.title}</div>
+                </div>
+                <div className={`buttons-container ${this.props.enabled ? "pointer-events-enabled" : "pointer-events-disabled"}`}>
+                    <div className="action-input frame-input">
+                        <input
+                            ref={this._frameInput}
+                            type="number"
+                            onChange={this.handleFrameChange}
+                            value={this.state.frame}
+                            max={this.state.max}
+                            min={this.state.min}
+                            step="1"
+                            disabled={this.props.actionableKeyframe.frame === undefined}
+                            onBlur={this.onBlur}
+                        />
+                    </div>
+                    <div className="action-input">
+                        <input
+                            ref={this._valueInput}
+                            type="number"
+                            value={this.state.value}
+                            onChange={this.handleValueChange}
+                            step="0.01"
+                            disabled={this.props.actionableKeyframe.value === undefined}
+                            onBlur={this.onBlur}
+                        />
+                    </div>
+                    <IconButtonLineComponent tooltip={"Add Keyframe"} icon="new-key" onClick={this.props.addKeyframe} />
+                    <IconButtonLineComponent
+                        tooltip={"Frame selected keyframes"}
+                        icon="frame"
+                        onClick={this.props.frameSelectedKeyframes}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={this.props.brokenMode ? "Flat selected control point" : "Flat control points"}
+                        icon="flat-tangent"
+                        onClick={this.props.flatTangent}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off"}
+                        icon={this.props.brokenMode ? "break-tangent" : "unify-tangent"}
+                        onClick={this.props.brokeTangents}
+                    />
+                    <IconButtonLineComponent
+                        tooltip={"Linear"}
+                        icon="linear-tangent"
+                        onClick={this.props.setLerpToActiveControlPoint}
+                    />
+                </div>
+            </div>
+        );
     }
-  }
-
-  render() {
-    return (
-      <div className='actions-wrapper'>
-        <div className='title-container'>
-          <div className='icon babylon-logo'></div>
-          <div className='title'>{this.props.title}</div>
-        </div>
-        <div
-          className='buttons-container'
-          style={{ pointerEvents: this.props.enabled ? 'all' : 'none' }}
-        >
-          <div className='action-input frame-input'>
-            <input
-              ref={this._frameInput}
-              type='number'
-              onChange={this.props.handleFrameChange}
-              value={this.props.actionableKeyframe.frame?.toString() || ''}
-              step='1'
-              disabled={this.props.actionableKeyframe.frame === undefined}
-              onBlur={(e) => this.onBlur(e)}
-            />
-          </div>
-          <div className='action-input'>
-            <input
-              ref={this._valueInput}
-              type='number'
-              value={this.props.actionableKeyframe.value || ''}
-              onChange={this.props.handleValueChange}
-              step='0.01'
-              disabled={this.props.actionableKeyframe.value === undefined}
-              onBlur={(e) => this.onBlur(e)}
-            />
-          </div>
-          <IconButtonLineComponent
-            tooltip={'Add Keyframe'}
-            icon='new-key'
-            onClick={this.props.addKeyframe}
-          />
-          <IconButtonLineComponent
-            tooltip={'Frame selected keyframes'}
-            icon='frame'
-            onClick={this.props.removeKeyframe}
-          />
-          <IconButtonLineComponent
-            tooltip={'Flat Tangents'}
-            icon='flat-tangent'
-            onClick={this.props.flatTangent}
-          />
-          <IconButtonLineComponent
-            tooltip={
-              this.props.brokenMode ? 'Broken Mode On' : 'Broken Mode Off'
-            }
-            icon={this.props.brokenMode ? 'break-tangent' : 'unify-tangent'}
-            onClick={this.props.brokeTangents}
-          />
-          <IconButtonLineComponent
-            tooltip={this.props.lerpMode ? 'Lerp On' : 'lerp Off'}
-            icon='linear-tangent'
-            onClick={this.props.setLerpMode}
-          />
-        </div>
-      </div>
-    );
-  }
 }

+ 93 - 86
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -1,101 +1,108 @@
-import * as React from 'react';
-import { Vector2 } from 'babylonjs/Maths/math.vector';
-import { AnchorSvgPoint } from './anchorSvgPoint';
+import * as React from "react";
+import { Vector2 } from "babylonjs/Maths/math.vector";
+import { AnchorSvgPoint } from "./anchorSvgPoint";
 
-const keyInactive = require('./assets/keyInactiveIcon.svg') as string;
-//const keyActive = require("./assets/keyActiveIcon.svg") as string; uncomment when setting active multiselect
-const keySelected = require('./assets/keySelectedIcon.svg') as string;
+const keyInactive = require("./assets/keyInactiveIcon.svg") as string;
+const keySelected = require("./assets/keySelectedIcon.svg") as string;
 
 export interface IKeyframeSvgPoint {
-  keyframePoint: Vector2;
-  rightControlPoint: Vector2 | null;
-  leftControlPoint: Vector2 | null;
-  id: string;
-  selected: boolean;
-  isLeftActive: boolean;
-  isRightActive: boolean;
-  curveId?: ICurveMetaData;
+    keyframePoint: Vector2;
+    rightControlPoint: Vector2 | null;
+    leftControlPoint: Vector2 | null;
+    id: string;
+    selected: boolean;
+    isLeftActive: boolean;
+    isRightActive: boolean;
+    curveId?: ICurveMetaData;
 }
 
 export interface ICurveMetaData {
-  id: number;
-  animationName: string;
-  property: string;
+    id: number;
+    animationName: string;
+    property: string;
 }
 
 interface IKeyframeSvgPointProps {
-  keyframePoint: Vector2;
-  leftControlPoint: Vector2 | null;
-  rightControlPoint: Vector2 | null;
-  id: string;
-  selected: boolean;
-  selectKeyframe: (id: string, multiselect: boolean) => void;
-  selectedControlPoint: (type: string, id: string) => void;
-  isLeftActive: boolean;
-  isRightActive: boolean;
+    keyframePoint: Vector2;
+    leftControlPoint: Vector2 | null;
+    rightControlPoint: Vector2 | null;
+    id: string;
+    selected: boolean;
+    selectKeyframe: (id: string, multiselect: boolean) => void;
+    selectedControlPoint: (type: string, id: string) => void;
+    isLeftActive: boolean;
+    isRightActive: boolean;
+    framesInCanvasView: { from: number; to: number };
 }
 
+/**
+ * Renders the Keyframe as an SVG Element for the Canvas component.
+ * Holds the two control points to generate the proper curve.
+ */
 export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
-  constructor(props: IKeyframeSvgPointProps) {
-    super(props);
-  }
-
-  select(e: React.MouseEvent<SVGImageElement>) {
-    e.preventDefault();
-    let multiSelect = false;
-    if (e.buttons === 0 && e.ctrlKey) {
-      multiSelect = true;
+    constructor(props: IKeyframeSvgPointProps) {
+        super(props);
     }
-    this.props.selectKeyframe(this.props.id, multiSelect);
-  }
 
-  render() {
-    return (
-      <>
-        <svg
-          className='draggable'
-          x={this.props.keyframePoint.x}
-          y={this.props.keyframePoint.y}
-          style={{ overflow: 'visible', cursor: 'pointer' }}
-        >
-          <image
-            data-id={this.props.id}
-            className='draggable'
-            x='-1'
-            y='-1.5'
-            width='3'
-            height='3'
-            href={this.props.selected ? keySelected : keyInactive}
-            onClick={(e) => this.select(e)}
-          />
-        </svg>
-        {this.props.leftControlPoint && (
-          <AnchorSvgPoint
-            type='left'
-            index={this.props.id}
-            control={this.props.leftControlPoint}
-            anchor={this.props.keyframePoint}
-            active={this.props.selected}
-            selected={this.props.isLeftActive}
-            selectControlPoint={(type: string) =>
-              this.props.selectedControlPoint(type, this.props.id)
-            }
-          />
-        )}
-        {this.props.rightControlPoint && (
-          <AnchorSvgPoint
-            type='right'
-            index={this.props.id}
-            control={this.props.rightControlPoint}
-            anchor={this.props.keyframePoint}
-            active={this.props.selected}
-            selected={this.props.isRightActive}
-            selectControlPoint={(type: string) =>
-              this.props.selectedControlPoint(type, this.props.id)
-            }
-          />
-        )}
-      </>
-    );
-  }
+    select = (e: React.MouseEvent<SVGImageElement>) => {
+        e.preventDefault();
+        let multiSelect = false;
+        if (e.buttons === 0 && e.ctrlKey) {
+            multiSelect = true;
+        }
+        this.props.selectKeyframe(this.props.id, multiSelect);
+    };
+
+    selectedControlPointId = (type: string) => {
+        this.props.selectedControlPoint(type, this.props.id);
+    };
+
+    render() {
+        const svgImageIcon = this.props.selected ? keySelected : keyInactive;
+        return (
+            <>
+                <svg
+                    className="draggable"
+                    x={this.props.keyframePoint.x}
+                    y={this.props.keyframePoint.y}
+                    style={{ overflow: "visible", cursor: "pointer" }}
+                >
+                    <image
+                        data-id={this.props.id}
+                        className="draggable"
+                        x="-1"
+                        y="-1.5"
+                        width="3"
+                        height="3"
+                        href={svgImageIcon}
+                        onClick={this.select}
+                    />
+                </svg>
+                {this.props.leftControlPoint && (
+                    <AnchorSvgPoint
+                        type="left"
+                        index={this.props.id}
+                        control={this.props.leftControlPoint}
+                        anchor={this.props.keyframePoint}
+                        active={this.props.selected}
+                        selected={this.props.isLeftActive}
+                        selectControlPoint={this.selectedControlPointId}
+                        framesInCanvasView={this.props.framesInCanvasView}
+                    />
+                )}
+                {this.props.rightControlPoint && (
+                    <AnchorSvgPoint
+                        type="right"
+                        index={this.props.id}
+                        control={this.props.rightControlPoint}
+                        anchor={this.props.keyframePoint}
+                        active={this.props.selected}
+                        selected={this.props.isRightActive}
+                        selectControlPoint={this.selectedControlPointId}
+                        framesInCanvasView={this.props.framesInCanvasView}
+                    />
+                )}
+            </>
+        );
+    }
 }

+ 104 - 114
inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx

@@ -1,126 +1,116 @@
-import * as React from 'react';
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
-import { Animation } from 'babylonjs/Animations/animation';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { FileButtonLineComponent } from '../../../lines/fileButtonLineComponent';
-import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
-import { LockObject } from '../lockObject';
-import { Tools } from 'babylonjs/Misc/tools';
-import { GlobalState } from '../../../../globalState';
-import { ReadFileError } from 'babylonjs/Misc/fileTools';
-import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
-import { TargetedAnimation } from 'babylonjs/Animations/animationGroup';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { Animation } from "babylonjs/Animations/animation";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { FileButtonLineComponent } from "../../../lines/fileButtonLineComponent";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
+import { LockObject } from "../lockObject";
+import { Tools } from "babylonjs/Misc/tools";
+import { GlobalState } from "../../../../globalState";
+import { ReadFileError } from "babylonjs/Misc/fileTools";
+import { IAnimatable } from "babylonjs/Animations/animatable.interface";
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
 
 interface ILoadSnippetProps {
-  animations: Animation[];
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
-  lockObject: LockObject;
-  globalState: GlobalState;
-  snippetServer: string;
-  setSnippetId: (id: string) => void;
-  entity: IAnimatable | TargetedAnimation;
-  setNotificationMessage: (message: string) => void;
-  animationsLoaded: (numberOfAnimations: number) => void;
+    animations: Animation[];
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    lockObject: LockObject;
+    globalState: GlobalState;
+    snippetServer: string;
+    setSnippetId: (id: string) => void;
+    entity: IAnimatable | TargetedAnimation;
+    setNotificationMessage: (message: string) => void;
+    animationsLoaded: (numberOfAnimations: number) => void;
 }
 
-export class LoadSnippet extends React.Component<
-  ILoadSnippetProps,
-  { snippetId: string }
-> {
-  private _serverAddress: string;
-  constructor(props: ILoadSnippetProps) {
-    super(props);
-    this._serverAddress = this.props.snippetServer;
-    this.state = { snippetId: '' };
-  }
+/**
+ * Loads animation locally or from the Babylon.js Snippet Server
+ */
+export class LoadSnippet extends React.Component<ILoadSnippetProps, { snippetId: string }> {
+    private _serverAddress: string;
+    constructor(props: ILoadSnippetProps) {
+        super(props);
+        this._serverAddress = this.props.snippetServer;
+        this.state = { snippetId: "" };
+    }
 
-  change(value: string) {
-    this.setState({ snippetId: value });
-    this.props.setSnippetId(value);
-  }
+    change = (value: string) => {
+        this.setState({ snippetId: value });
+        this.props.setSnippetId(value);
+    };
 
-  loadFromFile(file: File) {
-    Tools.ReadFile(
-      file,
-      (data) => {
-        let decoder = new TextDecoder('utf-8');
-        let jsonObject = JSON.parse(decoder.decode(data));
-        var result: Animation[] = [];
+    loadFromFile = (file: File) => {
+        Tools.ReadFile(
+            file,
+            (data) => {
+                let decoder = new TextDecoder("utf-8");
+                let jsonObject = JSON.parse(decoder.decode(data));
+                var result: Animation[] = [];
 
-        for (var i in jsonObject) {
-          result.push(Animation.Parse(jsonObject[i]));
-        }
+                for (var i in jsonObject) {
+                    result.push(Animation.Parse(jsonObject[i]));
+                }
 
-        if (this.props.entity) {
-          (this.props.entity as IAnimatable).animations = result;
-          var e = new PropertyChangedEvent();
-          e.object = this.props.entity;
-          e.property = 'animations';
-          e.value = (this.props.entity as IAnimatable).animations;
-          this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
-          this.props.animationsLoaded(result.length);
-        }
-      },
-      undefined,
-      true,
-      (error: ReadFileError) => {
-        console.log(error.message);
-      }
-    );
-  }
+                if (this.props.entity) {
+                    (this.props.entity as IAnimatable).animations = result;
+                    var e = new PropertyChangedEvent();
+                    e.object = this.props.entity;
+                    e.property = "animations";
+                    e.value = (this.props.entity as IAnimatable).animations;
+                    this.props.globalState.onPropertyChangedObservable.notifyObservers(e);
+                    this.props.animationsLoaded(result.length);
+                }
+            },
+            undefined,
+            true,
+            (error: ReadFileError) => {
+                console.log(error.message);
+            }
+        );
+    };
 
-  loadFromSnippet() {
-    if (this.state.snippetId !== '') {
-      //How to dispose() previous animations;
-      //How to notify observers
-      Animation.CreateFromSnippetAsync(this.state.snippetId)
-        .then((newAnimations) => {
-          // Explore how observers are notified from snippet
-          if (newAnimations instanceof Array) {
-            (this.props.entity as IAnimatable).animations = newAnimations;
-          }
+    loadFromSnippet = () => {
+        if (this.state.snippetId !== "") {
+            //Notify observers
+            Animation.CreateFromSnippetAsync(this.state.snippetId)
+                .then((newAnimations) => {
+                    // Explore how observers are notified from snippet
+                    if (newAnimations instanceof Array) {
+                        (this.props.entity as IAnimatable).animations = newAnimations;
+                    }
 
-          if (newAnimations instanceof Animation) {
-            (this.props.entity as IAnimatable).animations?.push(newAnimations);
-          }
-        })
-        .catch((err) => {
-          this.props.setNotificationMessage(
-            `Unable to load your animations: ${err}`
-          );
-        });
-    } else {
-      this.props.setNotificationMessage(`You need to add an snippet id`);
-    }
-  }
+                    if (newAnimations instanceof Animation) {
+                        (this.props.entity as IAnimatable).animations?.push(newAnimations);
+                    }
+                })
+                .catch((err) => {
+                    this.props.setNotificationMessage(`Unable to load your animations: ${err}`);
+                });
+        } else {
+            this.props.setNotificationMessage(`You need to add an snippet id`);
+        }
+    };
 
-  render() {
-    return (
-      <div className='load-container'>
-        <TextInputLineComponent
-          label='Snippet Id'
-          lockObject={this.props.lockObject}
-          value={this.state.snippetId}
-          onChange={(value: string) => this.change(value)}
-        />
-        <ButtonLineComponent
-          label='Load from snippet server'
-          onClick={() => this.loadFromSnippet()}
-        />
-        <div className='load-browse'>
-          <p>Local File</p>
-          <FileButtonLineComponent
-            label='Load'
-            onClick={(file) => this.loadFromFile(file)}
-            accept='.json'
-          />
-        </div>
-        <div className='load-server'>
-          <p>Snippet Server: </p>&nbsp;
-          <p> {this._serverAddress ?? '-'}</p>
-        </div>
-      </div>
-    );
-  }
+    render() {
+        return (
+            <div className="load-container">
+                <TextInputLineComponent
+                    label="Snippet Id"
+                    lockObject={this.props.lockObject}
+                    value={this.state.snippetId}
+                    onChange={this.change}
+                />
+                <ButtonLineComponent label="Load from snippet server" onClick={this.loadFromSnippet} />
+                <div className="load-browse">
+                    <p>Local File</p>
+                    <FileButtonLineComponent label="Load" onClick={this.loadFromFile} accept=".json" />
+                </div>
+                <div className="load-server">
+                    <p>Snippet Server: </p>&nbsp;
+                    <p> {this._serverAddress ?? "-"}</p>
+                </div>
+            </div>
+        );
+    }
 }

+ 22 - 25
inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx

@@ -1,32 +1,29 @@
-import * as React from 'react';
+import * as React from "react";
 
 interface IPlayheadProps {
-  message: string;
-  open: boolean;
-  close: () => void;
+    message: string;
+    open: boolean;
+    close: () => void;
 }
 
+/**
+ * Renders the notification for the user
+ */
 export class Notification extends React.Component<IPlayheadProps> {
-  constructor(props: IPlayheadProps) {
-    super(props);
-  }
+    constructor(props: IPlayheadProps) {
+        super(props);
+    }
 
-  render() {
-    return (
-      <div
-        className='notification-area'
-        style={{ display: this.props.open ? 'block' : 'none' }}>
-        <div className='alert alert-error'>
-          <button
-            type='button'
-            className='close'
-            data-dismiss='alert'
-            onClick={this.props.close}>
-            &times;
-          </button>
-          {this.props.message}
-        </div>
-      </div>
-    );
-  }
+    render() {
+        return (
+            <div className="notification-area" style={{ display: this.props.open ? "block" : "none" }}>
+                <div className="alert alert-error">
+                    <button type="button" className="close" data-dismiss="alert" onClick={this.props.close}>
+                        &times;
+                    </button>
+                    {this.props.message}
+                </div>
+            </div>
+        );
+    }
 }

+ 3 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/playhead.tsx

@@ -6,6 +6,9 @@ interface IPlayheadProps {
   onCurrentFrameChange: (frame: number) => void;
 }
 
+/**
+ * Renders the Playhead
+ */
 export class Playhead extends React.Component<IPlayheadProps> {
   private _direction: number;
   private _active: boolean;

+ 155 - 163
inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx

@@ -1,182 +1,174 @@
-import * as React from 'react';
-import { Observable } from 'babylonjs/Misc/observable';
-import { PropertyChangedEvent } from '../../../../../components/propertyChangedEvent';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { Tools } from 'babylonjs/Misc/tools';
-import { Animation } from 'babylonjs/Animations/animation';
-import { LockObject } from '../lockObject';
-import { Nullable } from 'babylonjs/types';
-import { GlobalState } from '../../../../globalState';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { PropertyChangedEvent } from "../../../../../components/propertyChangedEvent";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { Tools } from "babylonjs/Misc/tools";
+import { Animation } from "babylonjs/Animations/animation";
+import { LockObject } from "../lockObject";
+import { Nullable } from "babylonjs/types";
+import { GlobalState } from "../../../../globalState";
 
 interface ISaveSnippetProps {
-  animations: Nullable<Animation[]>;
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
-  lockObject: LockObject;
-  globalState: GlobalState;
-  snippetServer: string;
-  snippetId: string;
+    animations: Nullable<Animation[]>;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    lockObject: LockObject;
+    globalState: GlobalState;
+    snippetServer: string;
+    snippetId: string;
 }
 
 export interface Snippet {
-  url: string;
-  id: string;
+    url: string;
+    id: string;
 }
 interface SelectedAnimation {
-  id: string;
-  name: string;
-  index: number;
-  selected: boolean;
+    id: string;
+    name: string;
+    index: number;
+    selected: boolean;
 }
 
-export class SaveSnippet extends React.Component<
-  ISaveSnippetProps,
-  { selectedAnimations: SelectedAnimation[] }
-> {
-  constructor(props: ISaveSnippetProps) {
-    super(props);
-    let animList = this.props.animations?.map((animation, i) => {
-      return {
-        id: `${animation.name}_${animation.targetProperty}`,
-        name: animation.name,
-        index: i,
-        selected: false,
-      };
-    });
-    this.state = { selectedAnimations: animList ?? [] };
-  }
-
-  handleCheckboxChange(e: React.ChangeEvent<HTMLInputElement>) {
-    e.preventDefault();
-
-    let index = parseInt(e.target.id.replace('save_', ''));
-
-    let updated = this.state.selectedAnimations?.map((item) => {
-      if (item.index === index) {
-        item.selected = !item.selected;
-      }
-      return item;
-    });
-
-    this.setState({ selectedAnimations: updated });
-  }
-
-  stringifySelectedAnimations() {
-    const content: string[] = [];
-    this.state.selectedAnimations.forEach((animation) => {
-      if (animation.selected) {
-        const selected =
-          this.props.animations && this.props.animations[animation.index];
-        if (selected) {
-          content.push(selected.serialize());
-        }
-      }
-    });
-    return JSON.stringify(content);
-  }
-
-  saveToFile() {
-    const content = this.stringifySelectedAnimations();
-    Tools.Download(new Blob([content]), 'animations.json');
-  }
-
-  saveToSnippet() {
-    if (this.props.snippetId !== '') {
-      let serverId = this.props.snippetId;
-      const serverUrl = this.props.snippetServer;
-      const content = this.stringifySelectedAnimations();
-
-      var xmlHttp = new XMLHttpRequest();
-      xmlHttp.onreadystatechange = () => {
-        if (xmlHttp.readyState == 4) {
-          if (xmlHttp.status == 200) {
-            var snippet = JSON.parse(xmlHttp.responseText);
-            const oldId = serverId;
-            serverId = snippet.id;
-            if (snippet.version && snippet.version != '0') {
-              serverId += '#' + snippet.version;
-            }
-            this.forceUpdate();
-            if (navigator.clipboard) {
-              navigator.clipboard.writeText(serverId);
-            }
+/**
+ * Saves the animation snippet to the Babylon.js site or downloads the animation file locally
+ */
+export class SaveSnippet extends React.Component<ISaveSnippetProps, { selectedAnimations: SelectedAnimation[] }> {
+    constructor(props: ISaveSnippetProps) {
+        super(props);
+        let animList = this.props.animations?.map((animation, i) => {
+            return {
+                id: `${animation.name}_${animation.targetProperty}`,
+                name: animation.name,
+                index: i,
+                selected: false,
+            };
+        });
+        this.state = { selectedAnimations: animList ?? [] };
+    }
+
+    handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        e.preventDefault();
 
-            let windowAsAny = window as any;
+        let index = parseInt(e.target.id.replace("save_", ""));
 
-            if (windowAsAny.Playground && oldId) {
-              windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers(
-                {
-                  regex: new RegExp(oldId, 'g'),
-                  replace: serverId,
+        let updated = this.state.selectedAnimations?.map((item) => {
+            if (item.index === index) {
+                item.selected = !item.selected;
+            }
+            return item;
+        });
+
+        this.setState({ selectedAnimations: updated });
+    };
+
+    stringifySelectedAnimations() {
+        const content: string[] = [];
+        this.state.selectedAnimations.forEach((animation) => {
+            if (animation.selected) {
+                const selected = this.props.animations && this.props.animations[animation.index];
+                if (selected) {
+                    content.push(selected.serialize());
                 }
-              );
             }
+        });
+        return JSON.stringify(content);
+    }
 
-            alert(
-              'Animations saved with ID: ' +
-                serverId +
-                ' (please note that the id was also saved to your clipboard)'
-            );
-          } else {
-            alert('Unable to save your animations');
-          }
-        }
-      };
+    saveToFile = () => {
+        const content = this.stringifySelectedAnimations();
+        Tools.Download(new Blob([content]), "animations.json");
+    };
+
+    saveToSnippet = () => {
+        if (this.props.snippetId !== "") {
+            let serverId = this.props.snippetId;
+            const serverUrl = this.props.snippetServer;
+            const content = this.stringifySelectedAnimations();
+
+            var xmlHttp = new XMLHttpRequest();
+            xmlHttp.onreadystatechange = () => {
+                if (xmlHttp.readyState == 4) {
+                    if (xmlHttp.status == 200) {
+                        var snippet = JSON.parse(xmlHttp.responseText);
+                        const oldId = serverId;
+                        serverId = snippet.id;
+                        if (snippet.version && snippet.version != "0") {
+                            serverId += "#" + snippet.version;
+                        }
+                        this.forceUpdate();
+                        if (navigator.clipboard) {
+                            navigator.clipboard.writeText(serverId);
+                        }
+
+                        let windowAsAny = window as any;
+
+                        if (windowAsAny.Playground && oldId) {
+                            windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers({
+                                regex: new RegExp(oldId, "g"),
+                                replace: serverId,
+                            });
+                        }
+
+                        alert(
+                            "Animations saved with ID: " +
+                                serverId +
+                                " (please note that the id was also saved to your clipboard)"
+                        );
+                    } else {
+                        alert("Unable to save your animations");
+                    }
+                }
+            };
 
-      xmlHttp.open('POST', serverUrl + (serverId ? '/' + serverId : ''), true);
-      xmlHttp.setRequestHeader('Content-Type', 'application/json');
+            xmlHttp.open("POST", serverUrl + (serverId ? "/" + serverId : ""), true);
+            xmlHttp.setRequestHeader("Content-Type", "application/json");
 
-      var dataToSend = {
-        payload: JSON.stringify({
-          animations: content,
-        }),
-        name: '',
-        description: '',
-        tags: '',
-      };
+            var dataToSend = {
+                payload: JSON.stringify({
+                    animations: content,
+                }),
+                name: "",
+                description: "",
+                tags: "",
+            };
 
-      xmlHttp.send(JSON.stringify(dataToSend));
+            xmlHttp.send(JSON.stringify(dataToSend));
+        }
+    };
+
+    render() {
+        return (
+            <div className="save-container">
+                <div className="item-list">
+                    <ul>
+                        {this.props.animations?.map((animation, i) => {
+                            return (
+                                <li key={i}>
+                                    <div>
+                                        <label>
+                                            <input
+                                                id={`save_${i}`}
+                                                name={`save_${animation?.name}`}
+                                                type="checkbox"
+                                                checked={this.state.selectedAnimations[i].selected}
+                                                onChange={this.handleCheckboxChange}
+                                            />
+                                            {animation?.name}
+                                        </label>
+                                    </div>
+                                </li>
+                            );
+                        })}
+                    </ul>
+                </div>
+                <div className="save-buttons">
+                    {this.props.snippetId && <ButtonLineComponent label="Save to snippet server" onClick={this.saveToSnippet} />}
+                    <ButtonLineComponent label="Save" onClick={this.saveToFile} />
+                </div>
+                <div className="save-server">
+                    <p>Snippet Server: </p>&nbsp;
+                    <p> {this.props.snippetServer ?? "-"}</p>
+                </div>
+            </div>
+        );
     }
-  }
-
-  render() {
-    return (
-      <div className='save-container'>
-        <div className='item-list'>
-          <ul>
-            {this.props.animations?.map((animation, i) => {
-              return (
-                <li key={i}>
-                  <div>
-                    <label>
-                      <input
-                        id={`save_${i}`}
-                        name={`save_${animation?.name}`}
-                        type='checkbox'
-                        checked={this.state.selectedAnimations[i].selected}
-                        onChange={(e) => this.handleCheckboxChange(e)}
-                      />
-                      {animation?.name}
-                    </label>
-                  </div>
-                </li>
-              );
-            })}
-          </ul>
-        </div>
-        <div className='save-buttons'>
-          {this.props.snippetId !== '' ? (
-            <ButtonLineComponent
-              label='Save to snippet server'
-              onClick={() => this.saveToSnippet()}
-            />
-          ) : null}
-          <ButtonLineComponent label='Save' onClick={() => this.saveToFile()} />
-        </div>
-        <div className='save-server'>
-          <p>Snippet Server: </p>&nbsp;
-          <p> {this.props.snippetServer ?? '-'}</p>
-        </div>
-      </div>
-    );
-  }
 }

+ 37 - 36
inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx

@@ -1,44 +1,45 @@
-import * as React from 'react';
-import { CurveScale } from './animationCurveEditorComponent';
+import * as React from "react";
+import { CurveScale } from "./animationCurveEditorComponent";
 
 interface ISwitchButtonProps {
-  current: CurveScale;
-  action?: (event: CurveScale) => void;
+    current: CurveScale;
+    action?: (event: CurveScale) => void;
 }
 
-export class ScaleLabel extends React.Component<
-  ISwitchButtonProps,
-  { current: CurveScale }
-> {
-  constructor(props: ISwitchButtonProps) {
-    super(props);
-    this.state = { current: this.props.current };
-  }
+/**
+ * Displays the current scale
+ */
+export class ScaleLabel extends React.Component<ISwitchButtonProps, { current: CurveScale }> {
+    constructor(props: ISwitchButtonProps) {
+        super(props);
+        this.state = { current: this.props.current };
+    }
 
-  renderLabel(scale: CurveScale) {
-    switch (scale) {
-      case CurveScale.default:
-        return '';
-      case CurveScale.degrees:
-        return 'DEG';
-      case CurveScale.float:
-        return 'FLT';
-      case CurveScale.integers:
-        return 'INT';
-      case CurveScale.radians:
-        return 'RAD';
+    renderLabel(scale: CurveScale) {
+        switch (scale) {
+            case CurveScale.default:
+                return "";
+            case CurveScale.degrees:
+                return "DEG";
+            case CurveScale.float:
+                return "FLT";
+            case CurveScale.integers:
+                return "INT";
+            case CurveScale.radians:
+                return "RAD";
+        }
     }
-  }
 
-  render() {
-    return (
-      <div
-        className='switch-button'
-        onClick={() =>
-          this.props.action && this.props.action(this.state.current)
-        }>
-        <p>{this.renderLabel(this.state.current)}</p>
-      </div>
-    );
-  }
+    onClickHandle = () => {
+        this.props.action && this.props.action(this.state.current);
+    };
+
+    render() {
+        const label = this.renderLabel(this.state.current);
+        return (
+            <div className="switch-button" onClick={this.onClickHandle}>
+                <p>{label}</p>
+            </div>
+        );
+    }
 }

+ 114 - 98
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -1,25 +1,30 @@
 import * as React from "react";
 import { Vector2 } from "babylonjs/Maths/math.vector";
-import { KeyframeSvgPoint, IKeyframeSvgPoint } from "./keyframeSvgPoint";
+import { IKeyframeSvgPoint } from "./keyframeSvgPoint";
 
 interface ISvgDraggableAreaProps {
     keyframeSvgPoints: IKeyframeSvgPoint[];
     updatePosition: (updatedKeyframe: IKeyframeSvgPoint, id: string) => void;
     scale: number;
     viewBoxScale: number;
-    selectKeyframe: (id: string, multiselect: boolean) => void;
-    selectedControlPoint: (type: string, id: string) => void;
     deselectKeyframes: () => void;
     removeSelectedKeyframes: (points: IKeyframeSvgPoint[]) => void;
     panningY: (panningY: number) => void;
     panningX: (panningX: number) => void;
     setCurrentFrame: (direction: number) => void;
-    positionCanvas?: number;
+    positionCanvas?: Vector2;
     repositionCanvas?: boolean;
     canvasPositionEnded: () => void;
     resetActionableKeyframe: () => void;
+    framesInCanvasView: { from: number; to: number };
+    framesResized: number;
 }
 
+/**
+ * The SvgDraggableArea is a wrapper for SVG Canvas the interaction
+ *
+ * Here we control the drag and key behavior for the SVG components. 
+ */
 export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, { panX: number; panY: number }> {
     private _active: boolean;
     private _isCurrentPointControl: string;
@@ -31,6 +36,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     private _playheadSelected: boolean;
     private _movedX: number;
     private _movedY: number;
+    private _isControlKeyPress: boolean;
     readonly _dragBuffer: number;
     readonly _draggingMultiplier: number;
 
@@ -45,8 +51,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this._playheadSelected = false;
         this._movedX = 0;
         this._movedY = 0;
-        this._dragBuffer = 4;
-        this._draggingMultiplier = 3;
+        this._dragBuffer = 3;
+        this._draggingMultiplier = 10;
+        this._isControlKeyPress = false;
 
         this.state = { panX: 0, panY: 0 };
     }
@@ -59,78 +66,110 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }, 500);
     }
 
-    componentWillReceiveProps(newProps: ISvgDraggableAreaProps) {
-        if (newProps.positionCanvas !== this.props.positionCanvas && newProps.positionCanvas !== undefined && newProps.repositionCanvas) {
-            this.setState({ panX: newProps.positionCanvas }, () => {
-                this.props.canvasPositionEnded();
-            });
+    componentDidUpdate(prevProps: ISvgDraggableAreaProps) {
+        if (
+            this.props.positionCanvas !== prevProps.positionCanvas &&
+            this.props.positionCanvas !== undefined &&
+            this.props.repositionCanvas
+        ) {
+            this.setState(
+                {
+                    panX: this.props.positionCanvas.x,
+                    panY: this.props.positionCanvas.y,
+                },
+                () => {
+                    this.props.canvasPositionEnded();
+                }
+            );
         }
     }
 
-    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragStart(e: any): void {
+    dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.classList.contains("draggable")) {
+        if ((e.target as SVGSVGElement).classList.contains("draggable")) {
             this._active = true;
-            this._currentPointId = e.target.getAttribute("data-id");
+            const dataId = (e.target as SVGSVGElement).getAttribute("data-id");
+            if (dataId !== null) {
+                this._currentPointId = dataId;
+            }
 
-            if (e.target.classList.contains("control-point")) {
-                this._isCurrentPointControl = e.target.getAttribute("type");
+            if ((e.target as SVGSVGElement).classList.contains("control-point")) {
+                const type = (e.target as SVGSVGElement).getAttribute("type");
+                if (type !== null) {
+                    this._isCurrentPointControl = type;
+                }
             }
         }
 
-        if (e.target.classList.contains("svg-playhead")) {
+        if ((e.target as SVGSVGElement).classList.contains("svg-playhead")) {
             this._active = true;
             this._playheadSelected = true;
             this._playheadDrag = e.clientX - e.currentTarget.getBoundingClientRect().left;
         }
 
-        if (e.target.classList.contains("pannable")) {
-            this._active = true;
-            this._panStart.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
+        if ((e.target as SVGSVGElement).classList.contains("pannable")) {
+            if (this._isControlKeyPress) {
+                this._active = true;
+                this._panStart.set(
+                    e.clientX - e.currentTarget.getBoundingClientRect().left,
+                    e.clientY - e.currentTarget.getBoundingClientRect().top
+                );
+            }
         }
-    }
+    };
 
-    drag(e: React.TouchEvent<SVGSVGElement>): void;
-    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    drag(e: any): void {
+    drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         if (this._active) {
             e.preventDefault();
 
             var coord = this.getMousePosition(e);
 
             if (coord !== undefined) {
-                if (e.target.classList.contains("pannable")) {
-                    if (this._panStart.x !== 0 && this._panStart.y !== 0) {
-                        this._panStop.set(e.clientX - e.currentTarget.getBoundingClientRect().left, e.clientY - e.currentTarget.getBoundingClientRect().top);
-                        this.panDirection();
+                // Handles the canvas panning
+                if ((e.target as SVGSVGElement).classList.contains("pannable")) {
+                    if (this._isControlKeyPress) {
+                        if (this._panStart.x !== 0 && this._panStart.y !== 0) {
+                            this._panStop.set(
+                                e.clientX - e.currentTarget.getBoundingClientRect().left,
+                                e.clientY - e.currentTarget.getBoundingClientRect().top
+                            );
+                            this.panDirection();
+                        }
                     }
                 }
-                if (e.currentTarget.classList.contains("linear") && this._playheadDrag !== 0 && this._playheadSelected) {
+                // Handles the playhead dragging
+                if (
+                    e.currentTarget.classList.contains("linear") &&
+                    this._playheadDrag !== 0 &&
+                    this._playheadSelected
+                ) {
                     const moving = e.clientX - e.currentTarget.getBoundingClientRect().left;
 
-                    const distance = moving - this._playheadDrag;
                     const draggableAreaWidth = e.currentTarget.clientWidth;
-                    const framesInCavas = 20;
-                    const unit = draggableAreaWidth / framesInCavas;
 
-                    if (Math.abs(distance) >= unit / 1.25) {
-                        this.props.setCurrentFrame(Math.sign(distance));
-                        this._playheadDrag = this._playheadDrag + distance;
-                    }
+                    const initialFrame = this.props.framesInCanvasView.from;
+
+                    const lastFrame = this.props.framesInCanvasView.to;
+
+                    const framesInCanvas = lastFrame - initialFrame;
+
+                    const unit = draggableAreaWidth / framesInCanvas;
+
+                    const newFrame = Math.round(moving / unit) + initialFrame;
+                    this.props.setCurrentFrame(newFrame);
                 } else {
+                    // Handles the control point dragging
                     var newPoints = [...this.props.keyframeSvgPoints];
-
                     let point = newPoints.find((kf) => kf.id === this._currentPointId);
                     if (point) {
-                        // Check for NaN values here.
                         if (this._isCurrentPointControl === "left") {
                             point.leftControlPoint = coord;
                             point.isLeftActive = true;
+                            point.isRightActive = false;
                         } else if (this._isCurrentPointControl === "right") {
                             point.rightControlPoint = coord;
                             point.isRightActive = true;
+                            point.isLeftActive = false;
                         } else {
                             point.keyframePoint = coord;
                             point.isRightActive = false;
@@ -141,11 +180,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
                 }
             }
         }
-    }
+    };
 
-    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragEnd(e: any): void {
+    dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._active = false;
         this._currentPointId = "";
@@ -156,15 +193,9 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         this._playheadSelected = false;
         this._movedX = 0;
         this._movedY = 0;
-    }
-
-    getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
-    getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined;
-    getMousePosition(e: any): Vector2 | undefined {
-        if (e.touches) {
-            e = e.touches[0];
-        }
+    };
 
+    getMousePosition = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined => {
         if (this._draggableArea.current) {
             var svg = this._draggableArea.current as SVGSVGElement;
             var CTM = svg.getScreenCTM();
@@ -176,8 +207,12 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         } else {
             return undefined;
         }
-    }
+    };
 
+    /**
+    * Handles the canvas panning direction and sets the X and Y values to move the
+    * SVG canvas
+    */
     panDirection() {
         let directionX = 1;
         if (this._movedX < this._panStop.x) {
@@ -193,17 +228,17 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
             directionY = 1; //bottom
         }
 
-        const bufferX = Math.abs(this._movedX - this._panStop.x);
-        const bufferY = Math.abs(this._movedY - this._panStop.y);
+        const bufferX = this._movedX === 0 ? 1 : Math.abs(this._movedX - this._panStop.x);
+        const bufferY = this._movedY === 0 ? 1 : Math.abs(this._movedY - this._panStop.y);
 
         let xMulti = 0;
-        if (bufferX > this._dragBuffer) {
-            xMulti = this._draggingMultiplier;
+        if (bufferX >= this._dragBuffer) {
+            xMulti = Math.round(Math.abs(bufferX - this._dragBuffer) / 2.5);
         }
 
         let yMulti = 0;
-        if (bufferY > this._dragBuffer) {
-            yMulti = this._draggingMultiplier;
+        if (bufferY >= this._dragBuffer) {
+            yMulti = Math.round(Math.abs(bufferY - this._dragBuffer) / 2.5);
         }
 
         this._movedX = this._panStop.x;
@@ -223,15 +258,22 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
 
     keyDown(e: KeyboardEvent) {
         e.preventDefault();
-        if (e.keyCode === 17) {
+        if (e.keyCode === 17 || e.keyCode === 32) {
             this._draggableArea.current?.style.setProperty("cursor", "grab");
+            this._isControlKeyPress = true;
         }
     }
 
     keyUp(e: KeyboardEvent) {
         e.preventDefault();
-        if (e.keyCode === 17) {
+        if (e.keyCode === 17 || e.keyCode === 32) {
             this._draggableArea.current?.style.setProperty("cursor", "initial");
+            this._isControlKeyPress = false;
+            this._active = false;
+            this._panStart.set(0, 0);
+            this._panStop.set(0, 0);
+            this._movedX = 0;
+            this._movedY = 0;
         }
 
         if (e.keyCode === 8 || e.keyCode === 46) {
@@ -240,7 +282,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
         }
     }
 
-    focus(e: React.MouseEvent<SVGSVGElement>) {
+    focus = (e: React.MouseEvent<SVGSVGElement>) => {
         e.preventDefault();
         this._draggableArea.current?.focus();
 
@@ -251,7 +293,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
 
             this.props.resetActionableKeyframe();
         }
-    }
+    };
 
     isNotControlPointActive() {
         const activeControlPoints = this.props.keyframeSvgPoints.filter((x) => x.isLeftActive || x.isRightActive);
@@ -263,49 +305,23 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps, {
     }
 
     render() {
+        const viewBoxScaling = `${this.props.positionCanvas?.x} ${this.props.positionCanvas?.y} ${Math.round(
+            this.props.scale * 200
+        )} ${Math.round(this.props.scale * 100)}`;
         return (
             <>
                 <svg
-                    style={{
-                        width: 30,
-                        height: 364,
-                        position: "absolute",
-                        zIndex: 1,
-                        pointerEvents: "none",
-                    }}
-                >
-                    <rect x="0" y="0" width="38px" height="100%" fill="#ffffff1c"></rect>
-                </svg>
-                <svg
                     className="linear pannable"
                     ref={this._draggableArea}
                     tabIndex={0}
-                    onMouseMove={(e) => this.drag(e)}
-                    onTouchMove={(e) => this.drag(e)}
-                    onTouchStart={(e) => this.dragStart(e)}
-                    onTouchEnd={(e) => this.dragEnd(e)}
-                    onMouseDown={(e) => this.dragStart(e)}
-                    onMouseUp={(e) => this.dragEnd(e)}
-                    onMouseLeave={(e) => this.dragEnd(e)}
-                    onClick={(e) => this.focus(e)}
-                    viewBox={`${this.state.panX} ${this.state.panY} ${Math.round(this.props.scale * 200)} ${Math.round(this.props.scale * 100)}`}
+                    onMouseMove={this.drag}
+                    onMouseDown={this.dragStart}
+                    onMouseUp={this.dragEnd}
+                    onMouseLeave={this.dragEnd}
+                    onClick={this.focus}
+                    viewBox={viewBoxScaling}
                 >
                     {this.props.children}
-
-                    {this.props.keyframeSvgPoints.map((keyframe, i) => (
-                        <KeyframeSvgPoint
-                            key={`${keyframe.id}_${i}`}
-                            id={keyframe.id}
-                            keyframePoint={keyframe.keyframePoint}
-                            leftControlPoint={keyframe.leftControlPoint}
-                            rightControlPoint={keyframe.rightControlPoint}
-                            isLeftActive={keyframe.isLeftActive}
-                            isRightActive={keyframe.isRightActive}
-                            selected={keyframe.selected}
-                            selectedControlPoint={(type: string, id: string) => this.props.selectedControlPoint(type, id)}
-                            selectKeyframe={(id: string, multiselect: boolean) => this.props.selectKeyframe(id, multiselect)}
-                        />
-                    ))}
                 </svg>
             </>
         );

+ 80 - 131
inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx

@@ -1,145 +1,94 @@
-import * as React from 'react';
-
-import { Observable } from 'babylonjs/Misc/observable';
-import { TargetedAnimation } from 'babylonjs/Animations/animationGroup';
-import { Scene } from 'babylonjs/scene';
-
-import { PropertyChangedEvent } from '../../../../propertyChangedEvent';
-import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
-import { LineContainerComponent } from '../../../lineContainerComponent';
-import { TextLineComponent } from '../../../lines/textLineComponent';
-import { LockObject } from '../lockObject';
-import { GlobalState } from '../../../../globalState';
-import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
-import { PopupComponent } from '../../../../popupComponent';
-import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
-import { AnimationGroup } from 'babylonjs/Animations/animationGroup';
+import * as React from "react";
+import { Observable } from "babylonjs/Misc/observable";
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+import { Scene } from "babylonjs/scene";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from "../../../../globalState";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
+import { PopupComponent } from "../../../../popupComponent";
+import { AnimationCurveEditorComponent } from "../animations/animationCurveEditorComponent";
+import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 
 interface ITargetedAnimationGridComponentProps {
-  globalState: GlobalState;
-  targetedAnimation: TargetedAnimation;
-  scene: Scene;
-  lockObject: LockObject;
-  onSelectionChangedObservable?: Observable<any>;
-  onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    globalState: GlobalState;
+    targetedAnimation: TargetedAnimation;
+    scene: Scene;
+    lockObject: LockObject;
+    onSelectionChangedObservable?: Observable<any>;
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
 }
 
-export class TargetedAnimationGridComponent extends React.Component<
-  ITargetedAnimationGridComponentProps
-> {
-  private _isCurveEditorOpen: boolean;
-  private _animationGroup: AnimationGroup | undefined;
-  constructor(props: ITargetedAnimationGridComponentProps) {
-    super(props);
-    this._animationGroup = this.props.scene.animationGroups.find((ag) => {
-      let ta = ag.targetedAnimations.find(
-        (ta) => ta === this.props.targetedAnimation
-      );
-      return ta !== undefined;
-    });
-  }
+export class TargetedAnimationGridComponent extends React.Component<ITargetedAnimationGridComponentProps> {
+    private _isCurveEditorOpen: boolean;
+    private _animationGroup: AnimationGroup | undefined;
+    constructor(props: ITargetedAnimationGridComponentProps) {
+        super(props);
+        this._animationGroup = this.props.scene.animationGroups.find((ag) => {
+            let ta = ag.targetedAnimations.find((ta) => ta === this.props.targetedAnimation);
+            return ta !== undefined;
+        });
+    }
 
-  onOpenAnimationCurveEditor() {
-    this._isCurveEditorOpen = true;
-  }
+    onOpenAnimationCurveEditor = () => {
+        this._isCurveEditorOpen = true;
+    };
 
-  onCloseAnimationCurveEditor(window: Window | null) {
-    this._isCurveEditorOpen = false;
-    if (window !== null) {
-      window.close();
-    }
-  }
+    onCloseAnimationCurveEditor = (window: Window | null) => {
+        this._isCurveEditorOpen = false;
+        if (window !== null) {
+            window.close();
+        }
+    };
 
-  playOrPause() {
-    if (this._animationGroup) {
-      if (this._animationGroup.isPlaying) {
-        this._animationGroup.stop();
-      } else {
-        this._animationGroup.start();
-      }
-      this.forceUpdate();
-    }
-  }
+    playOrPause = () => {
+        if (this._animationGroup) {
+            if (this._animationGroup.isPlaying) {
+                this._animationGroup.stop();
+            } else {
+                this._animationGroup.start();
+            }
+            this.forceUpdate();
+        }
+    };
 
-  deleteAnimation() {
-    if (this._animationGroup) {
-      let index = this._animationGroup.targetedAnimations.indexOf(
-        this.props.targetedAnimation
-      );
+    deleteAnimation = () => {
+        if (this._animationGroup) {
+            let index = this._animationGroup.targetedAnimations.indexOf(this.props.targetedAnimation);
 
-      if (index > -1) {
-        this._animationGroup.targetedAnimations.splice(index, 1);
-        this.props.onSelectionChangedObservable?.notifyObservers(null);
+            if (index > -1) {
+                this._animationGroup.targetedAnimations.splice(index, 1);
+                this.props.onSelectionChangedObservable?.notifyObservers(null);
 
-        if (this._animationGroup.isPlaying) {
-          this._animationGroup.stop();
-          this._animationGroup.start();
+                if (this._animationGroup.isPlaying) {
+                    this._animationGroup.stop();
+                    this._animationGroup.start();
+                }
+            }
         }
-      }
-    }
-  }
+    };
 
-  render() {
-    const targetedAnimation = this.props.targetedAnimation;
+    render() {
+        const targetedAnimation = this.props.targetedAnimation;
 
-    return (
-      <div className='pane'>
-        <LineContainerComponent
-          globalState={this.props.globalState}
-          title='GENERAL'
-        >
-          <TextLineComponent
-            label='Class'
-            value={targetedAnimation.getClassName()}
-          />
-          <TextInputLineComponent
-            lockObject={this.props.lockObject}
-            label='Name'
-            target={targetedAnimation.animation}
-            propertyName='name'
-            onPropertyChangedObservable={this.props.onPropertyChangedObservable}
-          />
-          {targetedAnimation.target.name && (
-            <TextLineComponent
-              label='Target'
-              value={targetedAnimation.target.name}
-              onLink={() =>
-                this.props.globalState.onSelectionChangedObservable.notifyObservers(
-                  targetedAnimation
-                )
-              }
-            />
-          )}
-          <ButtonLineComponent
-            label='Edit animation'
-            onClick={() => this.onOpenAnimationCurveEditor()}
-          />
-          {this._isCurveEditorOpen && (
-            <PopupComponent
-              id='curve-editor'
-              title='Curve Animation Editor'
-              size={{ width: 1024, height: 512 }}
-              onOpen={(window: Window) => {}}
-              onClose={(window: Window) =>
-                this.onCloseAnimationCurveEditor(window)
-              }
-            >
-              <AnimationCurveEditorComponent
-                scene={this.props.scene}
-                entity={targetedAnimation as any}
-                playOrPause={() => this.playOrPause()}
-                lockObject={this.props.lockObject}
-                globalState={this.props.globalState}
-                close={(event) => this.onCloseAnimationCurveEditor(event.view)}
-              />
-            </PopupComponent>
-          )}
-          <ButtonLineComponent
-            label='Dispose'
-            onClick={() => this.deleteAnimation()}
-          />
-        </LineContainerComponent>
-      </div>
-    );
-  }
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="Class" value={targetedAnimation.getClassName()} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={targetedAnimation.animation} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    {targetedAnimation.target.name && <TextLineComponent label="Target" value={targetedAnimation.target.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)} />}
+                    <ButtonLineComponent label="Edit animation" onClick={this.onOpenAnimationCurveEditor} />
+                    {this._isCurveEditorOpen && (
+                        <PopupComponent id="curve-editor" title="Curve Animation Editor" size={{ width: 1024, height: 512 }} onOpen={(window: Window) => {}} onClose={this.onCloseAnimationCurveEditor}>
+                            <AnimationCurveEditorComponent scene={this.props.scene} entity={targetedAnimation as any} playOrPause={this.playOrPause} lockObject={this.props.lockObject} globalState={this.props.globalState} />
+                        </PopupComponent>
+                    )}
+                    <ButtonLineComponent label="Dispose" onClick={this.deleteAnimation} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
 }

+ 148 - 74
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -13,9 +13,16 @@ interface ITimelineProps {
     isPlaying: boolean;
     animationLimit: number;
     fps: number;
-    repositionCanvas: (frame: number) => void;
+    repositionCanvas: (keyframe: IAnimationKey) => void;
+    resizeWindowProportion: number;
 }
 
+/**
+ * The Timeline for the curve editor
+ *
+ * Has a scrollbar that can be resized and move to left and right. 
+ * The timeline does not affect the Canvas but only the frame container.
+ */
 export class Timeline extends React.Component<
     ITimelineProps,
     {
@@ -28,14 +35,17 @@ export class Timeline extends React.Component<
         limitValue: number;
     }
 > {
+    // Div Elements to display the timeline
     private _scrollable: React.RefObject<HTMLDivElement>;
     private _scrollbarHandle: React.RefObject<HTMLDivElement>;
     private _scrollContainer: React.RefObject<HTMLDivElement>;
     private _inputAnimationLimit: React.RefObject<HTMLInputElement>;
+    // Direction of drag and resize of timeline
     private _direction: number;
     private _scrolling: boolean;
     private _shiftX: number;
     private _active: string = "";
+    // Margin of scrollbar and container
     readonly _marginScrollbar: number;
 
     constructor(props: ITimelineProps) {
@@ -50,7 +60,9 @@ export class Timeline extends React.Component<
         this._shiftX = 0;
         this._marginScrollbar = 3;
 
+        // Limit as Int because is related to Frames.
         const limit = Math.round(this.props.animationLimit / 2);
+        const scrollWidth = this.calculateScrollWidth(0, limit);
 
         if (this.props.selected !== null) {
             this.state = {
@@ -58,7 +70,7 @@ export class Timeline extends React.Component<
                 activeKeyframe: null,
                 start: 0,
                 end: limit,
-                scrollWidth: this.calculateScrollWidth(0, limit),
+                scrollWidth: scrollWidth,
                 selectionLength: this.range(0, limit),
                 limitValue: this.props.animationLimit,
             };
@@ -66,20 +78,32 @@ export class Timeline extends React.Component<
     }
 
     componentDidMount() {
-        this.setState({
-            scrollWidth: this.calculateScrollWidth(this.state.start, this.state.end),
-        });
+        setTimeout(() => {
+            this.setState({
+                scrollWidth: this.calculateScrollWidth(this.state.start, this.state.end),
+            });
+        }, 0);
 
         this._inputAnimationLimit.current?.addEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
+    componentDidUpdate(prevProps: ITimelineProps) {
+        if (prevProps.animationLimit !== this.props.animationLimit) {
+            this.setState({ limitValue: this.props.animationLimit });
+        }
+        if (prevProps.resizeWindowProportion !== this.props.resizeWindowProportion) {
+            if (this.state.scrollWidth !== undefined) {
+                this.setState({ scrollWidth: this.calculateScrollWidth(this.state.start, this.state.end) });
+            }
+        }
+    }
+
     componentWillUnmount() {
         this._inputAnimationLimit.current?.removeEventListener("keyup", this.isEnterKeyUp.bind(this));
     }
 
     isEnterKeyUp(event: KeyboardEvent) {
         event.preventDefault();
-
         if (event.key === "Enter") {
             this.setControlState();
         }
@@ -104,12 +128,18 @@ export class Timeline extends React.Component<
                     scrollWidth: this.calculateScrollWidth(0, newEnd),
                 });
                 if (this._scrollbarHandle.current && this._scrollContainer.current) {
-                    this._scrollbarHandle.current.style.left = `${this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar}px`;
+                    this._scrollbarHandle.current.style.left = `${
+                        this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar
+                    }px`;
                 }
             }
         );
     }
 
+    /**
+    * @param {number} start Frame from which the scrollbar should begin.
+    * @param {number} end Last frame for the timeline.
+    */
     calculateScrollWidth(start: number, end: number) {
         if (this._scrollContainer.current && this.props.animationLimit !== 0) {
             const containerMarginLeftRight = this._marginScrollbar * 2;
@@ -141,17 +171,21 @@ export class Timeline extends React.Component<
         }
     }
 
-    setCurrentFrame(event: React.MouseEvent<HTMLDivElement>) {
+    setCurrentFrame = (event: React.MouseEvent<HTMLDivElement>) => {
         event.preventDefault();
         if (this._scrollable.current) {
-            const containerWidth = this._scrollable.current?.clientWidth;
-            const unit = Math.round(containerWidth / this.state.selectionLength.length);
-            const frame = Math.round((event.clientX - 233) / unit) + this.state.start;
+            this._scrollable.current.focus();
+            const containerWidth = this._scrollable.current?.clientWidth - 20;
+            const framesOnView = this.state.selectionLength.length;
+            const unit = containerWidth / framesOnView;
+            const frame = Math.round((event.clientX - 230) / unit) + this.state.start;
             this.props.onCurrentFrameChange(frame);
-            this.props.repositionCanvas(frame);
         }
-    }
+    };
 
+    /**
+    * Handles the change of number of frames available in the timeline.
+    */
     handleLimitChange(event: React.ChangeEvent<HTMLInputElement>) {
         event.preventDefault();
         let newLimit = parseInt(event.target.value);
@@ -163,20 +197,16 @@ export class Timeline extends React.Component<
         });
     }
 
-    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
-    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragStart(e: any): void {
+    dragStart = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
-        this.setState({ activeKeyframe: parseInt(e.target.id.replace("kf_", "")) });
+        this.setState({ activeKeyframe: parseInt((e.target as SVGSVGElement).id.replace("kf_", "")) });
         this._direction = e.clientX;
-    }
+    };
 
-    drag(e: React.TouchEvent<SVGSVGElement>): void;
-    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    drag(e: any): void {
+    drag = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         if (this.props.keyframes) {
-            if (this.state.activeKeyframe === parseInt(e.target.id.replace("kf_", ""))) {
+            if (this.state.activeKeyframe === parseInt((e.target as SVGSVGElement).id.replace("kf_", ""))) {
                 let updatedKeyframe = this.props.keyframes[this.state.activeKeyframe];
                 if (this._direction > e.clientX) {
                     let used = this.isFrameBeingUsed(updatedKeyframe.frame - 1, -1);
@@ -193,8 +223,11 @@ export class Timeline extends React.Component<
                 this.props.dragKeyframe(updatedKeyframe.frame, this.state.activeKeyframe);
             }
         }
-    }
+    };
 
+    /**
+    * Check if the frame is being used as a Keyframe by the animation
+    */
     isFrameBeingUsed(frame: number, direction: number) {
         let used = this.props.keyframes?.find((kf) => kf.frame === frame);
         if (used) {
@@ -205,42 +238,38 @@ export class Timeline extends React.Component<
         }
     }
 
-    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
-    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
-    dragEnd(e: any): void {
+    dragEnd = (e: React.MouseEvent<SVGSVGElement, MouseEvent>): void => {
         e.preventDefault();
         this._direction = 0;
         this.setState({ activeKeyframe: null });
-    }
+    };
 
-    scrollDragStart(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDragStart(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDragStart(e: any) {
+    scrollDragStart = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.className === "scrollbar") {
-            if ((e.target.class = "scrollbar") && this._scrollbarHandle.current) {
+        this._scrollContainer.current && this._scrollContainer.current.focus();
+
+        if ((e.target as HTMLDivElement).className === "scrollbar") {
+            if (this._scrollbarHandle.current) {
                 this._scrolling = true;
                 this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
                 this._scrollbarHandle.current.style.left = e.pageX - this._shiftX + "px";
             }
         }
 
-        if (e.target.className === "left-draggable" && this._scrollbarHandle.current) {
+        if ((e.target as HTMLDivElement).className === "left-draggable" && this._scrollbarHandle.current) {
             this._active = "leftDraggable";
-            this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
+            this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left - 3;
         }
 
-        if (e.target.className === "right-draggable" && this._scrollbarHandle.current) {
+        if ((e.target as HTMLDivElement).className === "right-draggable" && this._scrollbarHandle.current) {
             this._active = "rightDraggable";
-            this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left;
+            this._shiftX = e.clientX - this._scrollbarHandle.current.getBoundingClientRect().left + 3;
         }
-    }
+    };
 
-    scrollDrag(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDrag(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDrag(e: any) {
+    scrollDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
-        if (e.target.className === "scrollbar") {
+        if ((e.target as HTMLDivElement).className === "scrollbar") {
             this.moveScrollbar(e.pageX);
         }
 
@@ -251,17 +280,20 @@ export class Timeline extends React.Component<
         if (this._active === "rightDraggable") {
             this.resizeScrollbarRight(e.clientX);
         }
-    }
+    };
 
-    scrollDragEnd(e: React.TouchEvent<HTMLDivElement>): void;
-    scrollDragEnd(e: React.MouseEvent<HTMLDivElement, MouseEvent>): void;
-    scrollDragEnd(e: any) {
+    scrollDragEnd = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
         e.preventDefault();
         this._scrolling = false;
         this._active = "";
         this._shiftX = 0;
-    }
+    };
 
+    /**
+    * Sets the start, end and selection length of the scrollbar. This will control the width and
+    * height of the scrollbar as well as the number of frames available
+    * @param {number} pageX Controls the X axis of the scrollbar movement.
+    */
     moveScrollbar(pageX: number) {
         if (this._scrolling && this._scrollbarHandle.current && this._scrollContainer.current) {
             const moved = pageX - this._shiftX;
@@ -288,6 +320,9 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+    * Controls the resizing of the scrollbar from the right handle
+    */
     resizeScrollbarRight(clientX: number) {
         if (this._scrollContainer.current && this._scrollbarHandle.current) {
             const moving = clientX - this._scrollContainer.current.getBoundingClientRect().left;
@@ -315,6 +350,9 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+    * Controls the resizing of the scrollbar from the left handle
+    */
     resizeScrollbarLeft(clientX: number) {
         if (this._scrollContainer.current && this._scrollbarHandle.current) {
             const moving = clientX - this._scrollContainer.current.getBoundingClientRect().left;
@@ -332,7 +370,8 @@ export class Timeline extends React.Component<
             }
 
             if (!(framesTo >= this.state.end - 20)) {
-                let toleft = framesTo * unit + this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar * 2;
+                let toleft =
+                    framesTo * unit + this._scrollContainer.current.getBoundingClientRect().left + this._marginScrollbar * 2;
                 if (this._scrollbarHandle.current) {
                     this._scrollbarHandle.current.style.left = toleft + "px";
                 }
@@ -345,6 +384,9 @@ export class Timeline extends React.Component<
         }
     }
 
+    /**
+    * Returns array with the expected length between two numbers
+    */
     range(start: number, end: number) {
         return Array.from({ length: end - start }, (_, i) => start + i * 1);
     }
@@ -365,6 +407,8 @@ export class Timeline extends React.Component<
         }
     }
 
+    dragDomFalse = () => false;
+
     render() {
         return (
             <>
@@ -380,34 +424,51 @@ export class Timeline extends React.Component<
                         scrollable={this._scrollable}
                     />
                     <div className="timeline-wrapper">
-                        <div ref={this._scrollable} className="display-line" onClick={(e) => this.setCurrentFrame(e)}>
+                        <div ref={this._scrollable} className="display-line" onClick={this.setCurrentFrame} tabIndex={50}>
                             <svg
                                 style={{
                                     width: "100%",
                                     height: 40,
                                     backgroundColor: "#222222",
                                 }}
-                                onMouseMove={(e) => this.drag(e)}
-                                onTouchMove={(e) => this.drag(e)}
-                                onTouchStart={(e) => this.dragStart(e)}
-                                onTouchEnd={(e) => this.dragEnd(e)}
-                                onMouseDown={(e) => this.dragStart(e)}
-                                onMouseUp={(e) => this.dragEnd(e)}
-                                onMouseLeave={(e) => this.dragEnd(e)}
-                                onDragStart={() => false}
+                                onMouseMove={this.drag}
+                                onMouseDown={this.dragStart}
+                                onMouseUp={this.dragEnd}
+                                onMouseLeave={this.dragEnd}
                             >
                                 {this.state.selectionLength.map((frame, i) => {
                                     return (
                                         <svg key={`tl_${frame}`}>
                                             {
                                                 <>
-                                                    <text x={(i * 100) / this.state.selectionLength.length + "%"} y="18" style={{ fontSize: 10, fill: "#555555" }}>
-                                                        {frame}
-                                                    </text>
-                                                    <line x1={(i * 100) / this.state.selectionLength.length + "%"} y1="22" x2={(i * 100) / this.state.selectionLength.length + "%"} y2="40" style={{ stroke: "#555555", strokeWidth: 0.5 }} />
-
+                                                    {frame % Math.round(this.state.selectionLength.length / 20) === 0 ? (
+                                                        <>
+                                                            <text
+                                                                x={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y="18"
+                                                                style={{ fontSize: 10, fill: "#555555" }}
+                                                            >
+                                                                {frame}
+                                                            </text>
+                                                            <line
+                                                                x1={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y1="22"
+                                                                x2={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y2="40"
+                                                                style={{ stroke: "#555555", strokeWidth: 0.5 }}
+                                                            />
+                                                        </>
+                                                    ) : null}
                                                     {this.getCurrentFrame(frame) ? (
-                                                        <svg x={this._scrollable.current ? this._scrollable.current.clientWidth / this.state.selectionLength.length / 2 : 1}>
+                                                        <svg
+                                                            x={
+                                                                this._scrollable.current
+                                                                    ? this._scrollable.current.clientWidth /
+                                                                      this.state.selectionLength.length /
+                                                                      2
+                                                                    : 1
+                                                            }
+                                                        >
                                                             <line
                                                                 x1={(i * 100) / this.state.selectionLength.length + "%"}
                                                                 y1="0"
@@ -415,7 +476,10 @@ export class Timeline extends React.Component<
                                                                 y2="40"
                                                                 style={{
                                                                     stroke: "rgba(18, 80, 107, 0.26)",
-                                                                    strokeWidth: this._scrollable.current ? this._scrollable.current.clientWidth / this.state.selectionLength.length : 1,
+                                                                    strokeWidth: this._scrollable.current
+                                                                        ? this._scrollable.current.clientWidth /
+                                                                          this.state.selectionLength.length
+                                                                        : 1,
                                                                 }}
                                                             />
                                                         </svg>
@@ -423,7 +487,14 @@ export class Timeline extends React.Component<
 
                                                     {this.getKeyframe(frame) ? (
                                                         <svg key={`kf_${i}`} tabIndex={i + 40}>
-                                                            <line id={`kf_${i.toString()}`} x1={(i * 100) / this.state.selectionLength.length + "%"} y1="0" x2={(i * 100) / this.state.selectionLength.length + "%"} y2="40" style={{ stroke: "#ffc017", strokeWidth: 1 }} />
+                                                            <line
+                                                                id={`kf_${i.toString()}`}
+                                                                x1={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y1="0"
+                                                                x2={(i * 100) / this.state.selectionLength.length + "%"}
+                                                                y2="40"
+                                                                style={{ stroke: "#ffc017", strokeWidth: 1 }}
+                                                            />
                                                         </svg>
                                                     ) : null}
                                                 </>
@@ -436,16 +507,13 @@ export class Timeline extends React.Component<
 
                         <div
                             className="timeline-scroll-handle"
-                            onMouseMove={(e) => this.scrollDrag(e)}
-                            onTouchMove={(e) => this.scrollDrag(e)}
-                            onTouchStart={(e) => this.scrollDragStart(e)}
-                            onTouchEnd={(e) => this.scrollDragEnd(e)}
-                            onMouseDown={(e) => this.scrollDragStart(e)}
-                            onMouseUp={(e) => this.scrollDragEnd(e)}
-                            onMouseLeave={(e) => this.scrollDragEnd(e)}
-                            onDragStart={() => false}
+                            onMouseMove={this.scrollDrag}
+                            onMouseDown={this.scrollDragStart}
+                            onMouseUp={this.scrollDragEnd}
+                            onMouseLeave={this.scrollDragEnd}
+                            onDragStart={this.dragDomFalse}
                         >
-                            <div className="scroll-handle" ref={this._scrollContainer}>
+                            <div className="scroll-handle" ref={this._scrollContainer} tabIndex={60}>
                                 <div className="handle" ref={this._scrollbarHandle} style={{ width: this.state.scrollWidth }}>
                                     <div className="left-grabber">
                                         <div className="left-draggable">
@@ -470,7 +538,13 @@ export class Timeline extends React.Component<
                         </div>
 
                         <div className="input-frame">
-                            <input ref={this._inputAnimationLimit} type="number" value={this.state.limitValue} onChange={(e) => this.handleLimitChange(e)} onBlur={(e) => this.onInputBlur(e)}></input>
+                            <input
+                                ref={this._inputAnimationLimit}
+                                type="number"
+                                value={this.state.limitValue}
+                                onChange={(e) => this.handleLimitChange(e)}
+                                onBlur={(e) => this.onInputBlur(e)}
+                            ></input>
                         </div>
                     </div>
                 </div>

+ 1 - 1
materialsLibrary/src/triPlanar/triplanar.vertex.fx

@@ -93,7 +93,7 @@ void main(void)
 	   
 	worldTangent = (world * vec4(worldTangent, 1.0)).xyz;
     worldBinormal = (world * vec4(worldBinormal, 1.0)).xyz;
-	vec3 worldNormal = normalize(cross(worldTangent, worldBinormal));
+	vec3 worldNormal = (world * vec4(normalize(normal), 1.0)).xyz;
 
 	tangentSpace[0] = worldTangent;
     tangentSpace[1] = worldBinormal;

+ 5 - 2
materialsLibrary/src/water/water.vertex.fx

@@ -61,6 +61,7 @@ uniform float time;
 uniform float windForce;
 uniform float waveHeight;
 uniform float waveSpeed;
+uniform float waveCount;
 
 // Water varyings
 varying vec3 vPosition;
@@ -125,9 +126,11 @@ void main(void) {
 	gl_PointSize = pointSize;
 #endif
 
+	float finalWaveCount = 1.0 / (waveCount * 0.5);
+
 	vec3 p = position;
-	float newY = (sin(((p.x / 0.05) + time * waveSpeed)) * waveHeight * windDirection.x * 5.0)
-			   + (cos(((p.z / 0.05) +  time * waveSpeed)) * waveHeight * windDirection.y * 5.0);
+	float newY = (sin(((p.x / finalWaveCount) + time * waveSpeed)) * waveHeight * windDirection.x * 5.0)
+			   + (cos(((p.z / finalWaveCount) +  time * waveSpeed)) * waveHeight * windDirection.y * 5.0);
 	p.y += abs(newY);
 	
 	gl_Position = viewProjection * finalWorld * vec4(p, 1.0);

+ 30 - 22
materialsLibrary/src/water/waterMaterial.ts

@@ -104,27 +104,27 @@ export class WaterMaterial extends PushMaterial {
     public maxSimultaneousLights: number;
 
     /**
-    * @param {number}: Represents the wind force
-    */
+     * Defines the wind force.
+     */
     @serialize()
     public windForce: number = 6;
     /**
-    * @param {Vector2}: The direction of the wind in the plane (X, Z)
-    */
+     * Defines the direction of the wind in the plane (X, Z).
+     */
     @serializeAsVector2()
     public windDirection: Vector2 = new Vector2(0, 1);
     /**
-    * @param {number}: Wave height, represents the height of the waves
-    */
+     * Defines the height of the waves.
+     */
     @serialize()
     public waveHeight: number = 0.4;
     /**
-    * @param {number}: Bump height, represents the bump height related to the bump map
-    */
+     * Defines the bump height related to the bump map.
+     */
     @serialize()
     public bumpHeight: number = 0.4;
     /**
-     * @param {boolean}: Add a smaller moving bump to less steady waves.
+     * Defines wether or not: to add a smaller moving bump to less steady waves.
      */
     @serialize("bumpSuperimpose")
     private _bumpSuperimpose = false;
@@ -132,7 +132,7 @@ export class WaterMaterial extends PushMaterial {
     public bumpSuperimpose: boolean;
 
     /**
-     * @param {boolean}: Color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
+     * Defines wether or not color refraction and reflection differently with .waterColor2 and .colorBlendFactor2. Non-linear (physically correct) fresnel.
      */
     @serialize("fresnelSeparate")
     private _fresnelSeparate = false;
@@ -140,7 +140,7 @@ export class WaterMaterial extends PushMaterial {
     public fresnelSeparate: boolean;
 
     /**
-     * @param {boolean}: bump Waves modify the reflection.
+     * Defines wether or not bump Wwves modify the reflection.
      */
     @serialize("bumpAffectsReflection")
     private _bumpAffectsReflection = false;
@@ -148,36 +148,42 @@ export class WaterMaterial extends PushMaterial {
     public bumpAffectsReflection: boolean;
 
     /**
-    * @param {number}: The water color blended with the refraction (near)
-    */
+     * Defines the water color blended with the refraction (near).
+     */
     @serializeAsColor3()
     public waterColor: Color3 = new Color3(0.1, 0.1, 0.6);
     /**
-    * @param {number}: The blend factor related to the water color
-    */
+     * Defines the blend factor related to the water color.
+     */
     @serialize()
     public colorBlendFactor: number = 0.2;
     /**
-     * @param {number}: The water color blended with the reflection (far)
+     * Defines the water color blended with the reflection (far).
      */
     @serializeAsColor3()
     public waterColor2: Color3 = new Color3(0.1, 0.1, 0.6);
     /**
-     * @param {number}: The blend factor related to the water color (reflection, far)
+     * Defines the blend factor related to the water color (reflection, far).
      */
     @serialize()
     public colorBlendFactor2: number = 0.2;
     /**
-    * @param {number}: Represents the maximum length of a wave
-    */
+     * Defines the maximum length of a wave.
+     */
     @serialize()
     public waveLength: number = 0.1;
 
     /**
-    * @param {number}: Defines the waves speed
-    */
+     * Defines the waves speed.
+     */
     @serialize()
     public waveSpeed: number = 1.0;
+
+    /**
+     * Defines the number of times waves are repeated. This is typically used to adjust waves count according to the ground's size where the material is applied on.
+     */
+    @serialize()
+    public waveCount: number = 20;
     /**
      * Sets or gets whether or not automatic clipping should be enabled or not. Setting to true will save performances and
      * will avoid calculating useless pixels in the pixel shader of the water material.
@@ -441,7 +447,8 @@ export class WaterMaterial extends PushMaterial {
 
                 // Water
                 "worldReflectionViewProjection", "windDirection", "waveLength", "time", "windForce",
-                "cameraPosition", "bumpHeight", "waveHeight", "waterColor", "waterColor2", "colorBlendFactor", "colorBlendFactor2", "waveSpeed"
+                "cameraPosition", "bumpHeight", "waveHeight", "waterColor", "waterColor2", "colorBlendFactor", "colorBlendFactor2", "waveSpeed",
+                "waveCount"
             ];
             var samplers = ["normalSampler",
                 // Water
@@ -573,6 +580,7 @@ export class WaterMaterial extends PushMaterial {
         this._activeEffect.setColor4("waterColor2", this.waterColor2, 1.0);
         this._activeEffect.setFloat("colorBlendFactor2", this.colorBlendFactor2);
         this._activeEffect.setFloat("waveSpeed", this.waveSpeed);
+        this._activeEffect.setFloat("waveCount", this.waveCount);
 
         // image processing
         if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {

+ 3 - 0
nodeEditor/src/diagram/graphCanvas.tsx

@@ -414,6 +414,9 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         // Update graph
         let dagreNodes = graph.nodes().map(node => graph.node(node));
         dagreNodes.forEach((dagreNode: any) => {
+            if (!dagreNode) {
+                return;
+            }
             if (dagreNode.type === "node") {
                 for (var node of this._nodes) {
                     if (node.id === dagreNode.id) {

+ 13 - 0
src/Lights/Shadows/shadowGenerator.ts

@@ -808,6 +808,19 @@ export class ShadowGenerator implements IShadowGenerator {
     }
 
     /**
+     * Gets or sets the size of the texture what stores the shadows
+     */
+    public get mapSize(): number {
+        return this._mapSize;
+    }
+
+    public set mapSize(size: number) {
+        this._mapSize = size;
+        this._light._markMeshesAsLightDirty();
+        this.recreateShadowMap();
+    }
+
+    /**
      * Creates a ShadowGenerator object.
      * A ShadowGenerator is the required tool to use the shadows.
      * Each light casting shadows needs to use its own ShadowGenerator.

+ 6 - 0
src/Materials/materialHelper.ts

@@ -441,6 +441,7 @@ export class MaterialHelper {
         defines["SHADOWPCSS" + lightIndex] = false;
         defines["SHADOWPOISSON" + lightIndex] = false;
         defines["SHADOWESM" + lightIndex] = false;
+        defines["SHADOWCLOSEESM" + lightIndex] = false;
         defines["SHADOWCUBE" + lightIndex] = false;
         defines["SHADOWLOWQUALITY" + lightIndex] = false;
         defines["SHADOWMEDIUMQUALITY" + lightIndex] = false;
@@ -525,6 +526,7 @@ export class MaterialHelper {
                 defines["SHADOWPCSS" + index] = false;
                 defines["SHADOWPOISSON" + index] = false;
                 defines["SHADOWESM" + index] = false;
+                defines["SHADOWCLOSEESM" + index] = false;
                 defines["SHADOWCUBE" + index] = false;
                 defines["SHADOWLOWQUALITY" + index] = false;
                 defines["SHADOWMEDIUMQUALITY" + index] = false;
@@ -671,6 +673,10 @@ export class MaterialHelper {
                 if (defines["SHADOWESM" + lightIndex]) {
                     fallbacks.addFallback(rank, "SHADOWESM" + lightIndex);
                 }
+
+                if (defines["SHADOWCLOSEESM" + lightIndex]) {
+                    fallbacks.addFallback(rank, "SHADOWCLOSEESM" + lightIndex);
+                }
             }
         }
         return lightFallbackRank++;