Browse Source

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

sebavan 4 years ago
parent
commit
8d836c1fe7
91 changed files with 8791 additions and 5635 deletions
  1. 25 1
      dist/preview release/babylon.d.ts
  2. 1 1
      dist/preview release/babylon.js
  3. 104 15
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 50 2
      dist/preview release/babylon.module.d.ts
  6. 50 22
      dist/preview release/documentation.d.ts
  7. 1 1
      dist/preview release/glTF2Interface/package.json
  8. 48 48
      dist/preview release/gui/babylon.gui.js
  9. 1 1
      dist/preview release/gui/babylon.gui.js.map
  10. 2 2
      dist/preview release/gui/package.json
  11. 6 6
      dist/preview release/inspector/babylon.inspector.bundle.js
  12. 2238 1404
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  13. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  14. 407 114
      dist/preview release/inspector/babylon.inspector.d.ts
  15. 845 257
      dist/preview release/inspector/babylon.inspector.module.d.ts
  16. 7 7
      dist/preview release/inspector/package.json
  17. 8 8
      dist/preview release/loaders/babylon.glTF1FileLoader.js
  18. 1 1
      dist/preview release/loaders/babylon.glTF1FileLoader.js.map
  19. 23 23
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  20. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  21. 26 26
      dist/preview release/loaders/babylon.glTFFileLoader.js
  22. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  23. 5 5
      dist/preview release/loaders/babylon.objFileLoader.js
  24. 1 1
      dist/preview release/loaders/babylon.objFileLoader.js.map
  25. 4 4
      dist/preview release/loaders/babylon.stlFileLoader.js
  26. 1 1
      dist/preview release/loaders/babylon.stlFileLoader.js.map
  27. 29 29
      dist/preview release/loaders/babylonjs.loaders.js
  28. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  29. 3 3
      dist/preview release/loaders/package.json
  30. 1 1
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js
  31. 1 1
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js.map
  32. 1 1
      dist/preview release/materialsLibrary/babylon.triPlanarMaterial.min.js
  33. 32 23
      dist/preview release/materialsLibrary/babylon.waterMaterial.js
  34. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.js.map
  35. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  36. 25 21
      dist/preview release/materialsLibrary/babylonjs.materials.d.ts
  37. 33 24
      dist/preview release/materialsLibrary/babylonjs.materials.js
  38. 1 1
      dist/preview release/materialsLibrary/babylonjs.materials.js.map
  39. 1 1
      dist/preview release/materialsLibrary/babylonjs.materials.min.js
  40. 50 42
      dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts
  41. 2 2
      dist/preview release/materialsLibrary/package.json
  42. 2 0
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  43. 7 7
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  44. 25 13
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  45. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  46. 4 0
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  47. 2 2
      dist/preview release/nodeEditor/package.json
  48. 1 1
      dist/preview release/package.json
  49. 1 1
      dist/preview release/packagesSizeBaseLine.json
  50. 2 2
      dist/preview release/postProcessesLibrary/package.json
  51. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  52. 3 3
      dist/preview release/serializers/package.json
  53. 50 2
      dist/preview release/viewer/babylon.module.d.ts
  54. 35 35
      dist/preview release/viewer/babylon.viewer.js
  55. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  56. 6 0
      dist/preview release/what's new.md
  57. 38 33
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/addAnimation.tsx
  58. 85 50
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx
  59. 1140 363
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx
  60. 43 16
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx
  61. 94 18
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationListTree.tsx
  62. 211 316
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx
  63. 78 35
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/controls.tsx
  64. 1204 1154
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss
  65. 294 272
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/editorControls.tsx
  66. 174 124
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx
  67. 93 86
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx
  68. 104 114
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/loadsnippet.tsx
  69. 22 25
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/notification.tsx
  70. 3 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/playhead.tsx
  71. 155 163
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/saveSnippet.tsx
  72. 37 36
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/scale-label.tsx
  73. 114 98
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx
  74. 80 131
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx
  75. 148 74
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx
  76. 1 1
      materialsLibrary/src/triPlanar/triplanar.vertex.fx
  77. 5 2
      materialsLibrary/src/water/water.vertex.fx
  78. 30 22
      materialsLibrary/src/water/waterMaterial.ts
  79. 28 3
      nodeEditor/src/components/propertyTab/propertyTab.scss
  80. 3 0
      nodeEditor/src/diagram/graphCanvas.tsx
  81. 11 1
      nodeEditor/src/diagram/properties/gradientNodePropertyComponent.tsx
  82. 9 5
      nodeEditor/src/diagram/properties/gradientStepComponent.tsx
  83. 2 2
      package.json
  84. 2 2
      src/Engines/thinEngine.ts
  85. 1 1
      src/Helpers/photoDome.ts
  86. 326 294
      src/Helpers/textureDome.ts
  87. 1 1
      src/Helpers/videoDome.ts
  88. 13 0
      src/Lights/Shadows/shadowGenerator.ts
  89. 47 14
      src/Materials/Textures/texture.ts
  90. 6 0
      src/Materials/materialHelper.ts
  91. 6 2
      src/Sprites/spriteSceneComponent.ts

+ 25 - 1
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.
@@ -28072,6 +28077,10 @@ declare module BABYLON {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -28097,6 +28106,10 @@ declare module BABYLON {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -57952,6 +57965,7 @@ declare module BABYLON {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -57997,7 +58011,7 @@ declare module BABYLON {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -58011,6 +58025,14 @@ declare module BABYLON {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -58034,6 +58056,8 @@ declare module BABYLON {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.js


+ 104 - 15
dist/preview release/babylon.max.js

@@ -39851,7 +39851,7 @@ var ThinEngine = /** @class */ (function () {
          */
         // Not mixed with Version for tooling purpose.
         get: function () {
-            return "babylonjs@4.2.0-beta.6";
+            return "babylonjs@4.2.0-beta.9";
         },
         enumerable: false,
         configurable: true
@@ -39861,7 +39861,7 @@ var ThinEngine = /** @class */ (function () {
          * Returns the current version of the framework
          */
         get: function () {
-            return "4.2.0-beta.6";
+            return "4.2.0-beta.9";
         },
         enumerable: false,
         configurable: true
@@ -51114,7 +51114,7 @@ var PhotoDome = /** @class */ (function (_super) {
     });
     PhotoDome.prototype._initTexture = function (urlsOrElement, scene, options) {
         var _this = this;
-        return new _Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_1__["Texture"](urlsOrElement, scene, true, !this._useDirectMapping, undefined, undefined, function (message, exception) {
+        return new _Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_1__["Texture"](urlsOrElement, scene, !options.generateMipMaps, !this._useDirectMapping, undefined, undefined, function (message, exception) {
             _this.onLoadErrorObservable.notifyObservers(message || "Unknown error occured");
             if (_this.onError) {
                 _this.onError(message, exception);
@@ -51363,6 +51363,7 @@ var TextureDome = /** @class */ (function (_super) {
         var _this = _super.call(this, name, scene) || this;
         _this.onError = onError;
         _this._halfDome = false;
+        _this._crossEye = false;
         _this._useDirectMapping = false;
         _this._textureMode = TextureDome.MODE_MONOSCOPIC;
         /**
@@ -51410,6 +51411,7 @@ var TextureDome = /** @class */ (function (_super) {
         _this._halfDome = !!options.halfDomeMode;
         // enable or disable according to the settings
         _this._halfDomeMask.setEnabled(_this._halfDome);
+        _this._crossEye = !!options.crossEyeMode;
         // create
         _this._texture.anisotropicFilteringLevel = 1;
         _this._texture.onLoadObservable.addOnce(function () {
@@ -51491,7 +51493,7 @@ var TextureDome = /** @class */ (function (_super) {
         },
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -51521,6 +51523,22 @@ var TextureDome = /** @class */ (function (_super) {
         enumerable: false,
         configurable: true
     });
+    Object.defineProperty(TextureDome.prototype, "crossEye", {
+        /**
+         * Is it a cross-eye texture?
+         */
+        get: function () {
+            return this._crossEye;
+        },
+        /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set: function (enabled) {
+            this._crossEye = enabled;
+        },
+        enumerable: false,
+        configurable: true
+    });
     TextureDome.prototype._changeTextureMode = function (value) {
         var _this = this;
         this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
@@ -51530,6 +51548,7 @@ var TextureDome = /** @class */ (function (_super) {
         this._texture.vScale = 1;
         this._texture.uOffset = 0;
         this._texture.vOffset = 0;
+        this._texture.vAng = 0;
         switch (value) {
             case TextureDome.MODE_MONOSCOPIC:
                 if (this._halfDome) {
@@ -51542,9 +51561,18 @@ var TextureDome = /** @class */ (function (_super) {
                 // Use 0.99999 to boost perf by not switching program
                 this._texture.uScale = this._halfDome ? 0.99999 : 0.5;
                 var rightOffset_1 = this._halfDome ? 0.0 : 0.5;
-                var leftOffset_1 = this._halfDome ? 0.5 : 0.0;
+                var leftOffset_1 = this._halfDome ? -0.5 : 0.0;
                 this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add(function (camera) {
-                    _this._texture.uOffset = camera.isRightCamera ? rightOffset_1 : leftOffset_1;
+                    var isRightCamera = camera.isRightCamera;
+                    if (_this._crossEye) {
+                        isRightCamera = !isRightCamera;
+                    }
+                    if (isRightCamera) {
+                        _this._texture.uOffset = rightOffset_1;
+                    }
+                    else {
+                        _this._texture.uOffset = leftOffset_1;
+                    }
                 });
                 break;
             case TextureDome.MODE_TOPBOTTOM:
@@ -51552,7 +51580,12 @@ var TextureDome = /** @class */ (function (_super) {
                 // Use 0.99999 to boost perf by not switching program
                 this._texture.vScale = this._halfDome ? 0.99999 : 0.5;
                 this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add(function (camera) {
-                    _this._texture.vOffset = camera.isRightCamera ? 0.5 : 0.0;
+                    var isRightCamera = camera.isRightCamera;
+                    // allow "cross-eye" if left and right were switched in this mode
+                    if (_this._crossEye) {
+                        isRightCamera = !isRightCamera;
+                    }
+                    _this._texture.vOffset = isRightCamera ? 0.5 : 0.0;
                 });
                 break;
         }
@@ -51649,7 +51682,7 @@ var VideoDome = /** @class */ (function (_super) {
     VideoDome.prototype._initTexture = function (urlsOrElement, scene, options) {
         var _this = this;
         var tempOptions = { loop: options.loop, autoPlay: options.autoPlay, autoUpdateTexture: true, poster: options.poster };
-        var texture = new _Materials_Textures_videoTexture__WEBPACK_IMPORTED_MODULE_2__["VideoTexture"]((this.name || "videoDome") + "_texture", urlsOrElement, scene, false, this._useDirectMapping, _Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_1__["Texture"].TRILINEAR_SAMPLINGMODE, tempOptions);
+        var texture = new _Materials_Textures_videoTexture__WEBPACK_IMPORTED_MODULE_2__["VideoTexture"]((this.name || "videoDome") + "_texture", urlsOrElement, scene, options.generateMipMaps, this._useDirectMapping, _Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_1__["Texture"].TRILINEAR_SAMPLINGMODE, tempOptions);
         // optional configuration
         if (options.clickToPlay) {
             scene.onPointerUp = function () {
@@ -59560,6 +59593,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();
@@ -95982,6 +96030,10 @@ var Texture = /** @class */ (function (_super) {
          */
         _this.wRotationCenter = 0.5;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        _this.homogeneousRotationInUVTransform = false;
+        /**
          * List of inspectable custom properties (used by the Inspector)
          * @see https://doc.babylonjs.com/how_to/debug_layer#extensibility
          */
@@ -96003,6 +96055,10 @@ var Texture = /** @class */ (function (_super) {
         _this._cachedVAng = -1;
         _this._cachedWAng = -1;
         _this._cachedProjectionMatrixId = -1;
+        _this._cachedURotationCenter = -1;
+        _this._cachedVRotationCenter = -1;
+        _this._cachedWRotationCenter = -1;
+        _this._cachedHomogeneousRotationInUVTransform = false;
         _this._cachedCoordinatesMode = -1;
         /** @hidden */
         _this._initialSamplingMode = Texture.BILINEAR_SAMPLINGMODE;
@@ -96228,7 +96284,11 @@ var Texture = /** @class */ (function (_super) {
             this.vScale === this._cachedVScale &&
             this.uAng === this._cachedUAng &&
             this.vAng === this._cachedVAng &&
-            this.wAng === this._cachedWAng) {
+            this.wAng === this._cachedWAng &&
+            this.uRotationCenter === this._cachedURotationCenter &&
+            this.vRotationCenter === this._cachedVRotationCenter &&
+            this.wRotationCenter === this._cachedWRotationCenter &&
+            this.homogeneousRotationInUVTransform === this._cachedHomogeneousRotationInUVTransform) {
             return this._cachedTextureMatrix;
         }
         this._cachedUOffset = this.uOffset;
@@ -96238,6 +96298,10 @@ var Texture = /** @class */ (function (_super) {
         this._cachedUAng = this.uAng;
         this._cachedVAng = this.vAng;
         this._cachedWAng = this.wAng;
+        this._cachedURotationCenter = this.uRotationCenter;
+        this._cachedVRotationCenter = this.vRotationCenter;
+        this._cachedWRotationCenter = this.wRotationCenter;
+        this._cachedHomogeneousRotationInUVTransform = this.homogeneousRotationInUVTransform;
         if (!this._cachedTextureMatrix) {
             this._cachedTextureMatrix = _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].Zero();
             this._rowGenerationMatrix = new _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"]();
@@ -96246,12 +96310,26 @@ var Texture = /** @class */ (function (_super) {
             this._t2 = _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Vector3"].Zero();
         }
         _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].RotationYawPitchRollToRef(this.vAng, this.uAng, this.wAng, this._rowGenerationMatrix);
-        this._prepareRowForTextureGeneration(0, 0, 0, this._t0);
-        this._prepareRowForTextureGeneration(1.0, 0, 0, this._t1);
-        this._prepareRowForTextureGeneration(0, 1.0, 0, this._t2);
-        this._t1.subtractInPlace(this._t0);
-        this._t2.subtractInPlace(this._t0);
-        _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].FromValuesToRef(this._t1.x, this._t1.y, this._t1.z, 0.0, this._t2.x, this._t2.y, this._t2.z, 0.0, this._t0.x, this._t0.y, this._t0.z, 0.0, 0.0, 0.0, 0.0, 1.0, this._cachedTextureMatrix);
+        if (this.homogeneousRotationInUVTransform) {
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].TranslationToRef(-this._cachedURotationCenter, -this._cachedVRotationCenter, -this._cachedWRotationCenter, _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[0]);
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].TranslationToRef(this._cachedURotationCenter, this._cachedVRotationCenter, this._cachedWRotationCenter, _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[1]);
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].ScalingToRef(this._cachedUScale, this._cachedVScale, 0, _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[2]);
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].TranslationToRef(this._cachedUOffset, this._cachedVOffset, 0, _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[3]);
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[0].multiplyToRef(this._rowGenerationMatrix, this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(_Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[1], this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(_Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[2], this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(_Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["TmpVectors"].Matrix[3], this._cachedTextureMatrix);
+            // copy the translation row to the 3rd row of the matrix so that we don't need to update the shaders (which expects the translation to be on the 3rd row)
+            this._cachedTextureMatrix.setRowFromFloats(2, this._cachedTextureMatrix.m[12], this._cachedTextureMatrix.m[13], this._cachedTextureMatrix.m[14], 1);
+        }
+        else {
+            this._prepareRowForTextureGeneration(0, 0, 0, this._t0);
+            this._prepareRowForTextureGeneration(1.0, 0, 0, this._t1);
+            this._prepareRowForTextureGeneration(0, 1.0, 0, this._t2);
+            this._t1.subtractInPlace(this._t0);
+            this._t2.subtractInPlace(this._t0);
+            _Maths_math_vector__WEBPACK_IMPORTED_MODULE_3__["Matrix"].FromValuesToRef(this._t1.x, this._t1.y, this._t1.z, 0.0, this._t2.x, this._t2.y, this._t2.z, 0.0, this._t0.x, this._t0.y, this._t0.z, 0.0, 0.0, 0.0, 0.0, 1.0, this._cachedTextureMatrix);
+        }
         var scene = this.getScene();
         if (!scene) {
             return this._cachedTextureMatrix;
@@ -96637,6 +96715,9 @@ var Texture = /** @class */ (function (_super) {
     ], Texture.prototype, "wRotationCenter", void 0);
     Object(tslib__WEBPACK_IMPORTED_MODULE_0__["__decorate"])([
         Object(_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["serialize"])()
+    ], Texture.prototype, "homogeneousRotationInUVTransform", void 0);
+    Object(tslib__WEBPACK_IMPORTED_MODULE_0__["__decorate"])([
+        Object(_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__["serialize"])()
     ], Texture.prototype, "isBlocking", null);
     return Texture;
 }(_Materials_Textures_baseTexture__WEBPACK_IMPORTED_MODULE_4__["BaseTexture"]));
@@ -102900,6 +102981,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;
@@ -102980,6 +103062,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;
@@ -103091,6 +103174,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++;
@@ -188218,6 +188304,9 @@ _scene__WEBPACK_IMPORTED_MODULE_1__["Scene"].prototype._internalMultiPickSprites
     return pickingInfos;
 };
 _scene__WEBPACK_IMPORTED_MODULE_1__["Scene"].prototype.pickSprite = function (x, y, predicate, fastCheck, camera) {
+    if (!this._tempSpritePickingRay) {
+        return null;
+    }
     this.createPickingRayInCameraSpaceToRef(x, y, this._tempSpritePickingRay, camera);
     return this._internalPickSprites(this._tempSpritePickingRay, predicate, fastCheck, camera);
 };

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.max.js.map


+ 50 - 2
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.
@@ -29211,6 +29216,10 @@ declare module "babylonjs/Materials/Textures/texture" {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -29236,6 +29245,10 @@ declare module "babylonjs/Materials/Textures/texture" {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -60546,6 +60559,7 @@ declare module "babylonjs/Helpers/textureDome" {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -60591,7 +60605,7 @@ declare module "babylonjs/Helpers/textureDome" {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -60605,6 +60619,14 @@ declare module "babylonjs/Helpers/textureDome" {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -60628,6 +60650,8 @@ declare module "babylonjs/Helpers/textureDome" {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;
@@ -98582,6 +98606,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.
@@ -108690,6 +108719,10 @@ declare module BABYLON {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -108715,6 +108748,10 @@ declare module BABYLON {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -138570,6 +138607,7 @@ declare module BABYLON {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -138615,7 +138653,7 @@ declare module BABYLON {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -138629,6 +138667,14 @@ declare module BABYLON {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -138652,6 +138698,8 @@ declare module BABYLON {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;

+ 50 - 22
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.
@@ -28072,6 +28077,10 @@ declare module BABYLON {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -28097,6 +28106,10 @@ declare module BABYLON {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -57952,6 +57965,7 @@ declare module BABYLON {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -57997,7 +58011,7 @@ declare module BABYLON {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -58011,6 +58025,14 @@ declare module BABYLON {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -58034,6 +58056,8 @@ declare module BABYLON {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;
@@ -87111,61 +87135,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.
          */

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

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

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

@@ -7,7 +7,7 @@
 		exports["babylonjs-gui"] = factory(require("babylonjs"));
 	else
 		root["BABYLON"] = root["BABYLON"] || {}, root["BABYLON"]["GUI"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_perfCounter__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -400,7 +400,7 @@ module.exports = g;
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTextureInstrumentation", function() { return AdvancedDynamicTextureInstrumentation; });
-/* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -543,7 +543,7 @@ var AdvancedDynamicTextureInstrumentation = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTexture", function() { return AdvancedDynamicTexture; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _controls_container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controls/container */ "./2D/controls/container.ts");
 /* harmony import */ var _style__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./style */ "./2D/style.ts");
@@ -1532,7 +1532,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _textBlock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textBlock */ "./2D/controls/textBlock.ts");
 /* harmony import */ var _image__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./image */ "./2D/controls/image.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__);
 
 
@@ -1764,7 +1764,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Checkbox", function() { return Checkbox; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -1947,7 +1947,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ColorPicker", function() { return ColorPicker; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
@@ -3340,7 +3340,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -3757,7 +3757,7 @@ babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredTypes
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control", function() { return Control; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -5706,7 +5706,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DisplayGrid", function() { return DisplayGrid; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -5939,7 +5939,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -6036,7 +6036,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__);
 
 
@@ -6494,7 +6494,7 @@ babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__["_TypeStore"].RegisteredTypes[
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Image", function() { return Image; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 
@@ -7431,7 +7431,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputPassword", function() { return InputPassword; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -7470,7 +7470,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputText", function() { return InputText; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8483,7 +8483,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Line", function() { return Line; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8754,7 +8754,7 @@ babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registere
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLine", function() { return MultiLine; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _multiLinePoint__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../multiLinePoint */ "./2D/multiLinePoint.ts");
@@ -9024,7 +9024,7 @@ babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registe
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RadioButton", function() { return RadioButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -9231,7 +9231,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Rectangle", function() { return Rectangle; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -9381,7 +9381,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _scrollViewerWindow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./scrollViewerWindow */ "./2D/controls/scrollViewers/scrollViewerWindow.ts");
 /* harmony import */ var _sliders_scrollBar__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../sliders/scrollBar */ "./2D/controls/sliders/scrollBar.ts");
 /* harmony import */ var _sliders_imageScrollBar__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../sliders/imageScrollBar */ "./2D/controls/sliders/imageScrollBar.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__);
 
 
@@ -11012,7 +11012,7 @@ var SelectionPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BaseSlider", function() { return BaseSlider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -11346,7 +11346,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../measure */ "./2D/measure.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -11939,7 +11939,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Slider", function() { return Slider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -12211,7 +12211,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel", function() { return StackPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -12481,7 +12481,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextWrapping", function() { return TextWrapping; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextBlock", function() { return TextBlock; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -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__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


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

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

File diff suppressed because it is too large
+ 6 - 6
dist/preview release/inspector/babylon.inspector.bundle.js


File diff suppressed because it is too large
+ 2238 - 1404
dist/preview release/inspector/babylon.inspector.bundle.max.js


File diff suppressed because it is too large
+ 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;

File diff suppressed because it is too large
+ 845 - 257
dist/preview release/inspector/babylon.inspector.module.d.ts


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

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

+ 8 - 8
dist/preview release/loaders/babylon.glTF1FileLoader.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -482,7 +482,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderExtension", function() { return GLTFLoaderExtension; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoaderUtils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./glTFLoaderUtils */ "./glTF/1.0/glTFLoaderUtils.ts");
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
@@ -2307,7 +2307,7 @@ var EBlendingFunction;
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFUtils", function() { return GLTFUtils; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -2557,7 +2557,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFMaterialsCommonExtension", function() { return GLTFMaterialsCommonExtension; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFLoader */ "./glTF/1.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -2754,7 +2754,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderAnimationStartMode", function() { return GLTFLoaderAnimationStartMode; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderState", function() { return GLTFLoaderState; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFFileLoader", function() { return GLTFFileLoader; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFValidation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFValidation */ "./glTF/glTFValidation.ts");
 
@@ -3648,7 +3648,7 @@ if (babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["SceneLoader"]) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFValidation", function() { return GLTFValidation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 function validateAsync(data, rootUrl, fileName, getExternalResource) {
@@ -3878,14 +3878,14 @@ __webpack_require__.r(__webpack_exports__);
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.glTF1FileLoader.js.map


+ 23 - 23
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -400,7 +400,7 @@ module.exports = g;
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_lights_image_based", function() { return EXT_lights_image_based; });
-/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -524,7 +524,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_mesh_gpu_instancing", function() { return EXT_mesh_gpu_instancing; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -737,7 +737,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_draco_mesh_compression", function() { return KHR_draco_mesh_compression; });
-/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -831,7 +831,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_lights", function() { return KHR_lights; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -928,7 +928,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_clearcoat", function() { return KHR_materials_clearcoat; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1029,7 +1029,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1101,7 +1101,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_pbrSpecularGlossiness", function() { return KHR_materials_pbrSpecularGlossiness; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1190,7 +1190,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_sheen", function() { return KHR_materials_sheen; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1276,7 +1276,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return KHR_materials_specular; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1353,7 +1353,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_transmission", function() { return KHR_materials_transmission; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1637,7 +1637,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_2__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_unlit", function() { return KHR_materials_unlit; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -1720,7 +1720,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -2000,7 +2000,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return KHR_texture_transform; });
-/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2128,7 +2128,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return MSFT_audio_emitter; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2362,7 +2362,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return MSFT_lod; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2709,7 +2709,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return MSFT_minecraftMesh; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2762,7 +2762,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return MSFT_sRGBFactors; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2917,7 +2917,7 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayItem", function() { return ArrayItem; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
-/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
 
@@ -5144,7 +5144,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderAnimationStartMode", function() { return GLTFLoaderAnimationStartMode; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderState", function() { return GLTFLoaderState; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFFileLoader", function() { return GLTFFileLoader; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFValidation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFValidation */ "./glTF/glTFValidation.ts");
 
@@ -6038,7 +6038,7 @@ if (babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["SceneLoader"]) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFValidation", function() { return GLTFValidation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 function validateAsync(data, rootUrl, fileName, getExternalResource) {
@@ -6288,14 +6288,14 @@ __webpack_require__.r(__webpack_exports__);
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.js.map


+ 26 - 26
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -482,7 +482,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderExtension", function() { return GLTFLoaderExtension; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoaderUtils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./glTFLoaderUtils */ "./glTF/1.0/glTFLoaderUtils.ts");
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
@@ -2307,7 +2307,7 @@ var EBlendingFunction;
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFUtils", function() { return GLTFUtils; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -2557,7 +2557,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFMaterialsCommonExtension", function() { return GLTFMaterialsCommonExtension; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFLoader */ "./glTF/1.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -2751,7 +2751,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_lights_image_based", function() { return EXT_lights_image_based; });
-/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -2875,7 +2875,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_mesh_gpu_instancing", function() { return EXT_mesh_gpu_instancing; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3088,7 +3088,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_draco_mesh_compression", function() { return KHR_draco_mesh_compression; });
-/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3182,7 +3182,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_lights", function() { return KHR_lights; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3279,7 +3279,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_clearcoat", function() { return KHR_materials_clearcoat; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3380,7 +3380,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3452,7 +3452,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_pbrSpecularGlossiness", function() { return KHR_materials_pbrSpecularGlossiness; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3541,7 +3541,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_sheen", function() { return KHR_materials_sheen; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3627,7 +3627,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return KHR_materials_specular; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3704,7 +3704,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_transmission", function() { return KHR_materials_transmission; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -3988,7 +3988,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_2__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_unlit", function() { return KHR_materials_unlit; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4071,7 +4071,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -4351,7 +4351,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return KHR_texture_transform; });
-/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4479,7 +4479,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return MSFT_audio_emitter; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4713,7 +4713,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return MSFT_lod; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5060,7 +5060,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return MSFT_minecraftMesh; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5113,7 +5113,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return MSFT_sRGBFactors; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5268,7 +5268,7 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayItem", function() { return ArrayItem; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
-/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
 
@@ -7495,7 +7495,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderAnimationStartMode", function() { return GLTFLoaderAnimationStartMode; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderState", function() { return GLTFLoaderState; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFFileLoader", function() { return GLTFFileLoader; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFValidation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFValidation */ "./glTF/glTFValidation.ts");
 
@@ -8389,7 +8389,7 @@ if (babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["SceneLoader"]) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFValidation", function() { return GLTFValidation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 function validateAsync(data, rootUrl, fileName, getExternalResource) {
@@ -8673,14 +8673,14 @@ __webpack_require__.r(__webpack_exports__);
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.js.map


+ 5 - 5
dist/preview release/loaders/babylon.objFileLoader.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -158,7 +158,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return MTLFileLoader; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -394,7 +394,7 @@ var MTLFileLoader = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
-/* harmony import */ var babylonjs_Maths_math_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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
 
@@ -1293,14 +1293,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.js.map


+ 4 - 4
dist/preview release/loaders/babylon.stlFileLoader.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -154,7 +154,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "STLFileLoader", function() { return STLFileLoader; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -401,14 +401,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylon.stlFileLoader.js.map


+ 29 - 29
dist/preview release/loaders/babylonjs.loaders.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-loaders"] = factory(require("babylonjs"));
 	else
 		root["LOADERS"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -421,7 +421,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MTLFileLoader", function() { return MTLFileLoader; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -657,7 +657,7 @@ var MTLFileLoader = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OBJFileLoader", function() { return OBJFileLoader; });
-/* harmony import */ var babylonjs_Maths_math_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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _mtlFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./mtlFileLoader */ "./OBJ/mtlFileLoader.ts");
 
@@ -1552,7 +1552,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "STLFileLoader", function() { return STLFileLoader; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 
@@ -1862,7 +1862,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderExtension", function() { return GLTFLoaderExtension; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoaderUtils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./glTFLoaderUtils */ "./glTF/1.0/glTFLoaderUtils.ts");
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
@@ -3687,7 +3687,7 @@ var EBlendingFunction;
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFUtils", function() { return GLTFUtils; });
 /* harmony import */ var _glTFLoaderInterfaces__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./glTFLoaderInterfaces */ "./glTF/1.0/glTFLoaderInterfaces.ts");
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -3937,7 +3937,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFMaterialsCommonExtension", function() { return GLTFMaterialsCommonExtension; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFLoader */ "./glTF/1.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -4131,7 +4131,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_lights_image_based", function() { return EXT_lights_image_based; });
-/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.scalar */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_scalar__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4255,7 +4255,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EXT_mesh_gpu_instancing", function() { return EXT_mesh_gpu_instancing; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4468,7 +4468,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_draco_mesh_compression", function() { return KHR_draco_mesh_compression; });
-/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Meshes/Compression/dracoCompression */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_Compression_dracoCompression__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4562,7 +4562,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_lights", function() { return KHR_lights; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4659,7 +4659,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_clearcoat", function() { return KHR_materials_clearcoat; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4760,7 +4760,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4832,7 +4832,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_pbrSpecularGlossiness", function() { return KHR_materials_pbrSpecularGlossiness; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -4921,7 +4921,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_sheen", function() { return KHR_materials_sheen; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5007,7 +5007,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return KHR_materials_specular; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5084,7 +5084,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_transmission", function() { return KHR_materials_transmission; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5368,7 +5368,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_2__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_unlit", function() { return KHR_materials_unlit; });
-/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.color */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_color__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5451,7 +5451,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
-/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -5731,7 +5731,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return KHR_texture_transform; });
-/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/Textures/texture */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_Textures_texture__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -5859,7 +5859,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return MSFT_audio_emitter; });
-/* 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__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/tools");
 /* 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 _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -6093,7 +6093,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return MSFT_lod; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -6440,7 +6440,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return MSFT_minecraftMesh; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -6493,7 +6493,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return MSFT_sRGBFactors; });
-/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
@@ -6648,7 +6648,7 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayItem", function() { return ArrayItem; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoader", function() { return GLTFLoader; });
-/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/deferred */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_deferred__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFFileLoader */ "./glTF/glTFFileLoader.ts");
 
@@ -8875,7 +8875,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderAnimationStartMode", function() { return GLTFLoaderAnimationStartMode; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFLoaderState", function() { return GLTFLoaderState; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFFileLoader", function() { return GLTFFileLoader; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _glTFValidation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./glTFValidation */ "./glTF/glTFValidation.ts");
 
@@ -9769,7 +9769,7 @@ if (babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__["SceneLoader"]) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GLTFValidation", function() { return GLTFValidation; });
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/observable");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/tools");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_0__);
 
 function validateAsync(data, rootUrl, fileName, getExternalResource) {
@@ -10198,14 +10198,14 @@ __webpack_require__.r(__webpack_exports__);
 
 /***/ }),
 
-/***/ "babylonjs/Misc/observable":
+/***/ "babylonjs/Misc/tools":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_observable__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_tools__;
 
 /***/ })
 

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


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

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

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.js.map


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.triPlanarMaterial.min.js


File diff suppressed because it is too large
+ 32 - 23
dist/preview release/materialsLibrary/babylon.waterMaterial.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.waterMaterial.js.map


File diff suppressed because it is too large
+ 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.
          */

File diff suppressed because it is too large
+ 33 - 24
dist/preview release/materialsLibrary/babylonjs.materials.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylonjs.materials.js.map


File diff suppressed because it is too large
+ 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.
          */

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

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

+ 2 - 0
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts

@@ -936,6 +936,7 @@ declare module NODEEDITOR {
         onDelete: () => void;
         onUpdateStep: () => void;
         onCheckForReOrder: () => void;
+        onCopy?: () => void;
     }
     export class GradientStepComponent extends React.Component<IGradientStepComponentProps, {
         gradient: number;
@@ -965,6 +966,7 @@ declare module NODEEDITOR {
         componentWillUnmount(): void;
         forceRebuild(): void;
         deleteStep(step: BABYLON.GradientBlockColorStep): void;
+        copyStep(step: BABYLON.GradientBlockColorStep): void;
         addNewStep(): void;
         checkForReOrder(): void;
         render(): JSX.Element;

File diff suppressed because it is too large
+ 7 - 7
dist/preview release/nodeEditor/babylon.nodeEditor.js


File diff suppressed because it is too large
+ 25 - 13
dist/preview release/nodeEditor/babylon.nodeEditor.max.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


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

@@ -1151,6 +1151,7 @@ declare module "babylonjs-node-editor/diagram/properties/gradientStepComponent"
         onDelete: () => void;
         onUpdateStep: () => void;
         onCheckForReOrder: () => void;
+        onCopy?: () => void;
     }
     export class GradientStepComponent extends React.Component<IGradientStepComponentProps, {
         gradient: number;
@@ -1184,6 +1185,7 @@ declare module "babylonjs-node-editor/diagram/properties/gradientNodePropertyCom
         componentWillUnmount(): void;
         forceRebuild(): void;
         deleteStep(step: GradientBlockColorStep): void;
+        copyStep(step: GradientBlockColorStep): void;
         addNewStep(): void;
         checkForReOrder(): void;
         render(): JSX.Element;
@@ -2852,6 +2854,7 @@ declare module NODEEDITOR {
         onDelete: () => void;
         onUpdateStep: () => void;
         onCheckForReOrder: () => void;
+        onCopy?: () => void;
     }
     export class GradientStepComponent extends React.Component<IGradientStepComponentProps, {
         gradient: number;
@@ -2881,6 +2884,7 @@ declare module NODEEDITOR {
         componentWillUnmount(): void;
         forceRebuild(): void;
         deleteStep(step: BABYLON.GradientBlockColorStep): void;
+        copyStep(step: BABYLON.GradientBlockColorStep): void;
         addNewStep(): void;
         checkForReOrder(): void;
         render(): JSX.Element;

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

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

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

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

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

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

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

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

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

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

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

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

+ 50 - 2
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.
@@ -29211,6 +29216,10 @@ declare module "babylonjs/Materials/Textures/texture" {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -29236,6 +29245,10 @@ declare module "babylonjs/Materials/Textures/texture" {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -60546,6 +60559,7 @@ declare module "babylonjs/Helpers/textureDome" {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -60591,7 +60605,7 @@ declare module "babylonjs/Helpers/textureDome" {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -60605,6 +60619,14 @@ declare module "babylonjs/Helpers/textureDome" {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -60628,6 +60650,8 @@ declare module "babylonjs/Helpers/textureDome" {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;
@@ -98582,6 +98606,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.
@@ -108690,6 +108719,10 @@ declare module BABYLON {
          */
         wRotationCenter: number;
         /**
+         * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+         */
+        homogeneousRotationInUVTransform: boolean;
+        /**
          * Are mip maps generated for this texture or not.
          */
         get noMipmap(): boolean;
@@ -108715,6 +108748,10 @@ declare module BABYLON {
         private _cachedVAng;
         private _cachedWAng;
         private _cachedProjectionMatrixId;
+        private _cachedURotationCenter;
+        private _cachedVRotationCenter;
+        private _cachedWRotationCenter;
+        private _cachedHomogeneousRotationInUVTransform;
         private _cachedCoordinatesMode;
         /** @hidden */
         protected _initialSamplingMode: number;
@@ -138570,6 +138607,7 @@ declare module BABYLON {
          */
         static readonly MODE_SIDEBYSIDE: number;
         private _halfDome;
+        private _crossEye;
         protected _useDirectMapping: boolean;
         /**
          * The texture being displayed on the sphere
@@ -138615,7 +138653,7 @@ declare module BABYLON {
         get textureMode(): number;
         /**
          * Sets the current texture mode for the texture. It can be:
-          * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+         * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
          * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
          * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
          */
@@ -138629,6 +138667,14 @@ declare module BABYLON {
          */
         set halfDome(enabled: boolean);
         /**
+         * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+         */
+        set crossEye(enabled: boolean);
+        /**
+         * Is it a cross-eye texture?
+         */
+        get crossEye(): boolean;
+        /**
          * Oberserver used in Stereoscopic VR Mode.
          */
         private _onBeforeCameraRenderObserver;
@@ -138652,6 +138698,8 @@ declare module BABYLON {
             faceForward?: boolean;
             useDirectMapping?: boolean;
             halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
         }, scene: Scene, onError?: Nullable<(message?: string, exception?: any) => void>);
         protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
         protected _changeTextureMode(value: number): void;

File diff suppressed because it is too large
+ 35 - 35
dist/preview release/viewer/babylon.viewer.js


File diff suppressed because it is too large
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -51,6 +51,8 @@
 - Changed DeviceSourceManager getInput contract to no longer return nullable values for reals this time. Also added proper cleanup for DeviceSourceManager observables ([Drigax](https://github.com/drigax))
 - Default Loading screen supports any image size and ratio ([#8845](https://github.com/BabylonJS/Babylon.js/issues/8845)) ([RaananW](https://github.com/RaananW))
 - Added optional success and error callbacks for freezeActiveMeshes ([RaananW](https://github.com/RaananW))
+- Allow cross-eye mode in photo and video dome ([#8897](https://github.com/BabylonJS/Babylon.js/issues/8897)) ([RaananW](https://github.com/RaananW))
+- Added noMipMap option to the photo dome construction process ([#8972](https://github.com/BabylonJS/Babylon.js/issues/8972)) ([RaananW](https://github.com/RaananW))
 
 ### Engine
 
@@ -169,6 +171,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
 
@@ -225,6 +228,7 @@
 ### Textures
 
 - .HDR environment files will now give accurate PBR reflections ([CraigFeldpsar](https://github.com/craigfeldspar))
+- Added a `homogeneousRotationInUVTransform` property in the `Texture` to avoid deformations when rotating the texture with non-uniform scaling ([Popov72](https://github.com/Popov72))
 
 ### Audio
 
@@ -327,8 +331,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>
+            </>
+        );
+    }
 }

File diff suppressed because it is too large
+ 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>
         );
     }

File diff suppressed because it is too large
+ 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) {

+ 28 - 3
nodeEditor/src/components/propertyTab/propertyTab.scss

@@ -237,7 +237,7 @@
     .gradient-step {
         display: grid;
         grid-template-rows: 100%;
-        grid-template-columns: 30px 30px 40px auto 20px 5px;
+        grid-template-columns: 30px 30px 40px auto 20px 30px;
         padding-top: 5px;
         padding-left: 5px;
         padding-bottom: 5px;
@@ -270,17 +270,42 @@
             margin-right: 5px;
 
             input {
-                width: unset;
+                width: 90%;
             }
         }
 
-        .gradient-delete {            
+        .gradient-copy {            
             grid-row: 1;
             grid-column: 5;
             display: grid;
             align-content: center;
             justify-content: center;
+ 
+            .img {
+                height: 20px;
+                width: 20px;
+            }
+            .img:hover {
+                cursor: pointer;
+            }
+
         }
+        .gradient-delete {            
+            grid-row: 1;
+            grid-column: 6;
+            display: grid;
+            align-content: center;
+            justify-content: center;;
+            .img {
+                height: 20px;
+                width: 20px;
+            }
+            .img:hover {
+                cursor: pointer;
+            }
+
+        }
+
     }
 
     .floatLine {

+ 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) {

+ 11 - 1
nodeEditor/src/diagram/properties/gradientNodePropertyComponent.tsx

@@ -53,6 +53,16 @@ export class GradientPropertyTabComponent extends React.Component<IPropertyCompo
         }
     }
 
+    copyStep(step: GradientBlockColorStep) {
+        let gradientBlock = this.props.block as GradientBlock;
+
+        let newStep = new GradientBlockColorStep(1.0, step.color);
+        gradientBlock.colorSteps.push(newStep);
+        gradientBlock.colorStepsUpdated();
+        this.forceRebuild();
+        this.forceUpdate();
+    }
+
     addNewStep() {
         let gradientBlock = this.props.block as GradientBlock;
 
@@ -130,7 +140,7 @@ export class GradientPropertyTabComponent extends React.Component<IPropertyCompo
                                 <GradientStepComponent globalState={this.props.globalState}
                                 onCheckForReOrder={() => this.checkForReOrder()}
                                 onUpdateStep={() => this.forceRebuild()}
-                                key={"step-" + i} lineIndex={i} step={c} onDelete={() => this.deleteStep(c)}/>
+                                key={"step-" + i} lineIndex={i} step={c} onCopy={() => this.copyStep(c)} onDelete={() => this.deleteStep(c)}/>
                             );
                         })
                     }

+ 9 - 5
nodeEditor/src/diagram/properties/gradientStepComponent.tsx

@@ -1,11 +1,12 @@
 import * as React from 'react';
 import { GlobalState } from '../../globalState';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { Color3 } from 'babylonjs/Maths/math.color';
 import { GradientBlockColorStep } from 'babylonjs/Materials/Node/Blocks/gradientBlock';
 import { ColorPickerLineComponent } from '../../sharedComponents/colorPickerComponent';
 
+const deleteButton = require('../../../imgs/delete.svg');
+const copyIcon: string = require('../../sharedComponents/copy.svg');
+
 interface IGradientStepComponentProps {
     globalState: GlobalState;
     step: GradientBlockColorStep;
@@ -13,6 +14,7 @@ interface IGradientStepComponentProps {
     onDelete: () => void;
     onUpdateStep: () => void;
     onCheckForReOrder: () => void;
+    onCopy?: () => void;
 }
 
 export class GradientStepComponent extends React.Component<IGradientStepComponentProps, {gradient: number}> {
@@ -44,7 +46,6 @@ export class GradientStepComponent extends React.Component<IGradientStepComponen
 
     render() {
         let step = this.props.step;
-
         return (
             <div className="gradient-step">
                 <div className="step">
@@ -65,8 +66,11 @@ export class GradientStepComponent extends React.Component<IGradientStepComponen
                         onPointerUp={evt => this.onPointerUp()}
                         onChange={evt => this.updateStep(parseFloat(evt.target.value))} />
                 </div>
-                <div className="gradient-delete" onClick={() => this.props.onDelete()}>
-                    <FontAwesomeIcon icon={faTrash} />
+                <div className="gradient-copy" onClick={() => {if(this.props.onCopy) this.props.onCopy()}} title="Copy Step">
+                    <img className="img" src={copyIcon} />
+                </div>
+                <div className="gradient-delete" onClick={() => this.props.onDelete()} title={"Delete Step"}>
+                    <img className="img" src={deleteButton}/>
                 </div>
             </div>
         )

+ 2 - 2
package.json

@@ -7,7 +7,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.2.0-beta.6",
+    "version": "4.2.0-beta.9",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -113,4 +113,4 @@
         "xhr2": "^0.2.0",
         "xmlbuilder": "15.1.1"
     }
-}
+}

+ 2 - 2
src/Engines/thinEngine.ts

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

+ 1 - 1
src/Helpers/photoDome.ts

@@ -55,7 +55,7 @@ export class PhotoDome extends TextureDome<Texture> {
     }
 
     protected _initTexture(urlsOrElement: string, scene: Scene, options: any): Texture {
-        return new Texture(urlsOrElement, scene, true, !this._useDirectMapping, undefined, undefined, (message, exception) => {
+        return new Texture(urlsOrElement, scene, !options.generateMipMaps, !this._useDirectMapping, undefined, undefined, (message, exception) => {
             this.onLoadErrorObservable.notifyObservers(message || "Unknown error occured");
 
             if (this.onError) {

+ 326 - 294
src/Helpers/textureDome.ts

@@ -1,294 +1,326 @@
-import { Scene } from "../scene";
-import { TransformNode } from "../Meshes/transformNode";
-import { Mesh } from "../Meshes/mesh";
-import { Texture } from "../Materials/Textures/texture";
-import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
-import "../Meshes/Builders/sphereBuilder";
-import { Nullable } from "../types";
-import { Observer, Observable } from "../Misc/observable";
-import { Vector3 } from "../Maths/math.vector";
-import { Axis } from "../Maths/math";
-import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
-
-declare type Camera = import("../Cameras/camera").Camera;
-
-/**
- * Display a 360/180 degree texture on an approximately spherical surface, useful for VR applications or skyboxes.
- * As a subclass of TransformNode, this allow parenting to the camera or multiple textures with different locations in the scene.
- * This class achieves its effect with a Texture and a correctly configured BackgroundMaterial on an inverted sphere.
- * Potential additions to this helper include zoom and and non-infinite distance rendering effects.
- */
-export abstract class TextureDome<T extends Texture> extends TransformNode {
-    /**
-     * Define the source as a Monoscopic panoramic 360/180.
-     */
-    public static readonly MODE_MONOSCOPIC = 0;
-    /**
-     * Define the source as a Stereoscopic TopBottom/OverUnder panoramic 360/180.
-     */
-    public static readonly MODE_TOPBOTTOM = 1;
-    /**
-     * Define the source as a Stereoscopic Side by Side panoramic 360/180.
-     */
-    public static readonly MODE_SIDEBYSIDE = 2;
-
-    private _halfDome: boolean = false;
-
-    protected _useDirectMapping = false;
-
-    /**
-     * The texture being displayed on the sphere
-     */
-    protected _texture: T;
-
-    /**
-     * Gets the texture being displayed on the sphere
-     */
-    public get texture(): T {
-        return this._texture;
-    }
-
-    /**
-     * Sets the texture being displayed on the sphere
-     */
-    public set texture(newTexture: T) {
-        if (this._texture === newTexture) {
-            return;
-        }
-        this._texture = newTexture;
-        if (this._useDirectMapping) {
-            this._texture.wrapU = Texture.CLAMP_ADDRESSMODE;
-            this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this._material.diffuseTexture = this._texture;
-        } else {
-            this._texture.coordinatesMode = Texture.FIXED_EQUIRECTANGULAR_MIRRORED_MODE; // matches orientation
-            this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this._material.reflectionTexture = this._texture;
-        }
-    }
-
-    /**
-     * The skybox material
-     */
-    protected _material: BackgroundMaterial;
-
-    /**
-     * The surface used for the dome
-     */
-    protected _mesh: Mesh;
-    /**
-     * Gets the mesh used for the dome.
-     */
-    public get mesh(): Mesh {
-        return this._mesh;
-    }
-
-    /**
-     * A mesh that will be used to mask the back of the dome in case it is a 180 degree movie.
-     */
-    private _halfDomeMask: Mesh;
-
-    /**
-     * The current fov(field of view) multiplier, 0.0 - 2.0. Defaults to 1.0. Lower values "zoom in" and higher values "zoom out".
-     * Also see the options.resolution property.
-     */
-    public get fovMultiplier(): number {
-        return this._material.fovMultiplier;
-    }
-    public set fovMultiplier(value: number) {
-        this._material.fovMultiplier = value;
-    }
-
-    protected _textureMode = TextureDome.MODE_MONOSCOPIC;
-    /**
-     * Gets or set the current texture mode for the texture. It can be:
-     * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
-     * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
-     * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
-     */
-    public get textureMode(): number {
-        return this._textureMode;
-    }
-    /**
-     * Sets the current texture mode for the texture. It can be:
-      * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
-     * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
-     * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
-     */
-    public set textureMode(value: number) {
-        if (this._textureMode === value) {
-            return;
-        }
-
-        this._changeTextureMode(value);
-    }
-
-    /**
-     * Is it a 180 degrees dome (half dome) or 360 texture (full dome)
-     */
-    public get halfDome(): boolean {
-        return this._halfDome;
-    }
-
-    /**
-     * Set the halfDome mode. If set, only the front (180 degrees) will be displayed and the back will be blacked out.
-     */
-    public set halfDome(enabled: boolean) {
-        this._halfDome = enabled;
-        this._halfDomeMask.setEnabled(enabled);
-    }
-
-    /**
-     * Oberserver used in Stereoscopic VR Mode.
-     */
-    private _onBeforeCameraRenderObserver: Nullable<Observer<Camera>> = null;
-    /**
-     * Observable raised when an error occured while loading the 360 image
-     */
-    public onLoadErrorObservable = new Observable<string>();
-
-    /**
-     * Create an instance of this class and pass through the parameters to the relevant classes- Texture, StandardMaterial, and Mesh.
-     * @param name Element's name, child elements will append suffixes for their own names.
-     * @param textureUrlOrElement defines the url(s) or the (video) HTML element to use
-     * @param options An object containing optional or exposed sub element properties
-     */
-    constructor(
-        name: string,
-        textureUrlOrElement: string | string[] | HTMLVideoElement,
-        options: {
-            resolution?: number;
-            clickToPlay?: boolean;
-            autoPlay?: boolean;
-            loop?: boolean;
-            size?: number;
-            poster?: string;
-            faceForward?: boolean;
-            useDirectMapping?: boolean;
-            halfDomeMode?: boolean;
-        },
-        scene: Scene,
-        protected onError: Nullable<(message?: string, exception?: any) => void> = null
-    ) {
-        super(name, scene);
-
-        scene = this.getScene();
-
-        // set defaults and manage values
-        name = name || "textureDome";
-        options.resolution = Math.abs(options.resolution as any) | 0 || 32;
-        options.clickToPlay = Boolean(options.clickToPlay);
-        options.autoPlay = options.autoPlay === undefined ? true : Boolean(options.autoPlay);
-        options.loop = options.loop === undefined ? true : Boolean(options.loop);
-        options.size = Math.abs(options.size as any) || (scene.activeCamera ? scene.activeCamera.maxZ * 0.48 : 1000);
-
-        if (options.useDirectMapping === undefined) {
-            this._useDirectMapping = true;
-        } else {
-            this._useDirectMapping = options.useDirectMapping;
-        }
-
-        if (options.faceForward === undefined) {
-            options.faceForward = true;
-        }
-
-        this._setReady(false);
-        this._mesh = Mesh.CreateSphere(name + "_mesh", options.resolution, options.size, scene, false, Mesh.BACKSIDE);
-        // configure material
-        let material = (this._material = new BackgroundMaterial(name + "_material", scene));
-        material.useEquirectangularFOV = true;
-        material.fovMultiplier = 1.0;
-        material.opacityFresnel = false;
-
-        const texture = this._initTexture(textureUrlOrElement, scene, options);
-        this.texture = texture;
-
-        // configure mesh
-        this._mesh.material = material;
-        this._mesh.parent = this;
-
-        // create a (disabled until needed) mask to cover unneeded segments of 180 texture.
-        this._halfDomeMask = SphereBuilder.CreateSphere("", { slice: 0.5, diameter: options.size * 0.98, segments: options.resolution * 2, sideOrientation: Mesh.BACKSIDE }, scene);
-        this._halfDomeMask.rotate(Axis.X, -Math.PI / 2);
-        // set the parent, so it will always be positioned correctly AND will be disposed when the main sphere is disposed
-        this._halfDomeMask.parent = this._mesh;
-        this._halfDome = !!options.halfDomeMode;
-        // enable or disable according to the settings
-        this._halfDomeMask.setEnabled(this._halfDome);
-
-        // create
-        this._texture.anisotropicFilteringLevel = 1;
-        this._texture.onLoadObservable.addOnce(() => {
-            this._setReady(true);
-        });
-
-        // Initial rotation
-        if (options.faceForward && scene.activeCamera) {
-            let camera = scene.activeCamera;
-
-            let forward = Vector3.Forward();
-            var direction = Vector3.TransformNormal(forward, camera.getViewMatrix());
-            direction.normalize();
-
-            this.rotation.y = Math.acos(Vector3.Dot(forward, direction));
-        }
-
-        this._changeTextureMode(this._textureMode);
-    }
-
-    protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
-
-    protected _changeTextureMode(value: number): void {
-        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
-        this._textureMode = value;
-
-        // Default Setup and Reset.
-        this._texture.uScale = 1;
-        this._texture.vScale = 1;
-        this._texture.uOffset = 0;
-        this._texture.vOffset = 0;
-
-        switch (value) {
-            case TextureDome.MODE_MONOSCOPIC:
-                if (this._halfDome) {
-                    this._texture.uScale = 2;
-                    this._texture.uOffset = -1;
-                }
-                break;
-            case TextureDome.MODE_SIDEBYSIDE:
-                // in half-dome mode the uScale should be double of 360 texture
-                // Use 0.99999 to boost perf by not switching program
-                this._texture.uScale = this._halfDome ? 0.99999 : 0.5;
-                const rightOffset = this._halfDome ? 0.0 : 0.5;
-                const leftOffset = this._halfDome ? 0.5 : 0.0;
-                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
-                    this._texture.uOffset = camera.isRightCamera ? rightOffset : leftOffset;
-                });
-                break;
-            case TextureDome.MODE_TOPBOTTOM:
-                // in half-dome mode the vScale should be double of 360 texture
-                // Use 0.99999 to boost perf by not switching program
-                this._texture.vScale = this._halfDome ? 0.99999 : 0.5;
-                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
-                    this._texture.vOffset = camera.isRightCamera ? 0.5 : 0.0;
-                });
-                break;
-        }
-    }
-
-    /**
-     * Releases resources associated with this node.
-     * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
-     * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
-     */
-    public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
-        this._texture.dispose();
-        this._mesh.dispose();
-        this._material.dispose();
-
-        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
-        this.onLoadErrorObservable.clear();
-
-        super.dispose(doNotRecurse, disposeMaterialAndTextures);
-    }
-}
+import { Scene } from "../scene";
+import { TransformNode } from "../Meshes/transformNode";
+import { Mesh } from "../Meshes/mesh";
+import { Texture } from "../Materials/Textures/texture";
+import { BackgroundMaterial } from "../Materials/Background/backgroundMaterial";
+import "../Meshes/Builders/sphereBuilder";
+import { Nullable } from "../types";
+import { Observer, Observable } from "../Misc/observable";
+import { Vector3 } from "../Maths/math.vector";
+import { Axis } from "../Maths/math";
+import { SphereBuilder } from "../Meshes/Builders/sphereBuilder";
+
+declare type Camera = import("../Cameras/camera").Camera;
+
+/**
+ * Display a 360/180 degree texture on an approximately spherical surface, useful for VR applications or skyboxes.
+ * As a subclass of TransformNode, this allow parenting to the camera or multiple textures with different locations in the scene.
+ * This class achieves its effect with a Texture and a correctly configured BackgroundMaterial on an inverted sphere.
+ * Potential additions to this helper include zoom and and non-infinite distance rendering effects.
+ */
+export abstract class TextureDome<T extends Texture> extends TransformNode {
+    /**
+     * Define the source as a Monoscopic panoramic 360/180.
+     */
+    public static readonly MODE_MONOSCOPIC = 0;
+    /**
+     * Define the source as a Stereoscopic TopBottom/OverUnder panoramic 360/180.
+     */
+    public static readonly MODE_TOPBOTTOM = 1;
+    /**
+     * Define the source as a Stereoscopic Side by Side panoramic 360/180.
+     */
+    public static readonly MODE_SIDEBYSIDE = 2;
+
+    private _halfDome: boolean = false;
+    private _crossEye: boolean = false;
+
+    protected _useDirectMapping = false;
+
+    /**
+     * The texture being displayed on the sphere
+     */
+    protected _texture: T;
+
+    /**
+     * Gets the texture being displayed on the sphere
+     */
+    public get texture(): T {
+        return this._texture;
+    }
+
+    /**
+     * Sets the texture being displayed on the sphere
+     */
+    public set texture(newTexture: T) {
+        if (this._texture === newTexture) {
+            return;
+        }
+        this._texture = newTexture;
+        if (this._useDirectMapping) {
+            this._texture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._material.diffuseTexture = this._texture;
+        } else {
+            this._texture.coordinatesMode = Texture.FIXED_EQUIRECTANGULAR_MIRRORED_MODE; // matches orientation
+            this._texture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._material.reflectionTexture = this._texture;
+        }
+    }
+
+    /**
+     * The skybox material
+     */
+    protected _material: BackgroundMaterial;
+
+    /**
+     * The surface used for the dome
+     */
+    protected _mesh: Mesh;
+    /**
+     * Gets the mesh used for the dome.
+     */
+    public get mesh(): Mesh {
+        return this._mesh;
+    }
+
+    /**
+     * A mesh that will be used to mask the back of the dome in case it is a 180 degree movie.
+     */
+    private _halfDomeMask: Mesh;
+
+    /**
+     * The current fov(field of view) multiplier, 0.0 - 2.0. Defaults to 1.0. Lower values "zoom in" and higher values "zoom out".
+     * Also see the options.resolution property.
+     */
+    public get fovMultiplier(): number {
+        return this._material.fovMultiplier;
+    }
+    public set fovMultiplier(value: number) {
+        this._material.fovMultiplier = value;
+    }
+
+    protected _textureMode = TextureDome.MODE_MONOSCOPIC;
+    /**
+     * Gets or set the current texture mode for the texture. It can be:
+     * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+     * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
+     * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
+     */
+    public get textureMode(): number {
+        return this._textureMode;
+    }
+    /**
+     * Sets the current texture mode for the texture. It can be:
+     * * TextureDome.MODE_MONOSCOPIC : Define the texture source as a Monoscopic panoramic 360.
+     * * TextureDome.MODE_TOPBOTTOM  : Define the texture source as a Stereoscopic TopBottom/OverUnder panoramic 360.
+     * * TextureDome.MODE_SIDEBYSIDE : Define the texture source as a Stereoscopic Side by Side panoramic 360.
+     */
+    public set textureMode(value: number) {
+        if (this._textureMode === value) {
+            return;
+        }
+
+        this._changeTextureMode(value);
+    }
+
+    /**
+     * Is it a 180 degrees dome (half dome) or 360 texture (full dome)
+     */
+    public get halfDome(): boolean {
+        return this._halfDome;
+    }
+
+    /**
+     * Set the halfDome mode. If set, only the front (180 degrees) will be displayed and the back will be blacked out.
+     */
+    public set halfDome(enabled: boolean) {
+        this._halfDome = enabled;
+        this._halfDomeMask.setEnabled(enabled);
+    }
+
+    /**
+     * Set the cross-eye mode. If set, images that can be seen when crossing eyes will render correctly
+     */
+    public set crossEye(enabled: boolean) {
+        this._crossEye = enabled;
+    }
+
+    /**
+     * Is it a cross-eye texture?
+     */
+    public get crossEye(): boolean {
+        return this._crossEye;
+    }
+
+    /**
+     * Oberserver used in Stereoscopic VR Mode.
+     */
+    private _onBeforeCameraRenderObserver: Nullable<Observer<Camera>> = null;
+    /**
+     * Observable raised when an error occured while loading the 360 image
+     */
+    public onLoadErrorObservable = new Observable<string>();
+
+    /**
+     * Create an instance of this class and pass through the parameters to the relevant classes- Texture, StandardMaterial, and Mesh.
+     * @param name Element's name, child elements will append suffixes for their own names.
+     * @param textureUrlOrElement defines the url(s) or the (video) HTML element to use
+     * @param options An object containing optional or exposed sub element properties
+     */
+    constructor(
+        name: string,
+        textureUrlOrElement: string | string[] | HTMLVideoElement,
+        options: {
+            resolution?: number;
+            clickToPlay?: boolean;
+            autoPlay?: boolean;
+            loop?: boolean;
+            size?: number;
+            poster?: string;
+            faceForward?: boolean;
+            useDirectMapping?: boolean;
+            halfDomeMode?: boolean;
+            crossEyeMode?: boolean;
+            generateMipMaps?: boolean;
+        },
+        scene: Scene,
+        protected onError: Nullable<(message?: string, exception?: any) => void> = null
+    ) {
+        super(name, scene);
+
+        scene = this.getScene();
+
+        // set defaults and manage values
+        name = name || "textureDome";
+        options.resolution = Math.abs(options.resolution as any) | 0 || 32;
+        options.clickToPlay = Boolean(options.clickToPlay);
+        options.autoPlay = options.autoPlay === undefined ? true : Boolean(options.autoPlay);
+        options.loop = options.loop === undefined ? true : Boolean(options.loop);
+        options.size = Math.abs(options.size as any) || (scene.activeCamera ? scene.activeCamera.maxZ * 0.48 : 1000);
+
+        if (options.useDirectMapping === undefined) {
+            this._useDirectMapping = true;
+        } else {
+            this._useDirectMapping = options.useDirectMapping;
+        }
+
+        if (options.faceForward === undefined) {
+            options.faceForward = true;
+        }
+
+        this._setReady(false);
+        this._mesh = Mesh.CreateSphere(name + "_mesh", options.resolution, options.size, scene, false, Mesh.BACKSIDE);
+        // configure material
+        let material = (this._material = new BackgroundMaterial(name + "_material", scene));
+        material.useEquirectangularFOV = true;
+        material.fovMultiplier = 1.0;
+        material.opacityFresnel = false;
+
+        const texture = this._initTexture(textureUrlOrElement, scene, options);
+        this.texture = texture;
+
+        // configure mesh
+        this._mesh.material = material;
+        this._mesh.parent = this;
+
+        // create a (disabled until needed) mask to cover unneeded segments of 180 texture.
+        this._halfDomeMask = SphereBuilder.CreateSphere("", { slice: 0.5, diameter: options.size * 0.98, segments: options.resolution * 2, sideOrientation: Mesh.BACKSIDE }, scene);
+        this._halfDomeMask.rotate(Axis.X, -Math.PI / 2);
+        // set the parent, so it will always be positioned correctly AND will be disposed when the main sphere is disposed
+        this._halfDomeMask.parent = this._mesh;
+        this._halfDome = !!options.halfDomeMode;
+        // enable or disable according to the settings
+        this._halfDomeMask.setEnabled(this._halfDome);
+        this._crossEye = !!options.crossEyeMode;
+
+        // create
+        this._texture.anisotropicFilteringLevel = 1;
+        this._texture.onLoadObservable.addOnce(() => {
+            this._setReady(true);
+        });
+
+        // Initial rotation
+        if (options.faceForward && scene.activeCamera) {
+            let camera = scene.activeCamera;
+
+            let forward = Vector3.Forward();
+            var direction = Vector3.TransformNormal(forward, camera.getViewMatrix());
+            direction.normalize();
+
+            this.rotation.y = Math.acos(Vector3.Dot(forward, direction));
+        }
+
+        this._changeTextureMode(this._textureMode);
+    }
+
+    protected abstract _initTexture(urlsOrElement: string | string[] | HTMLElement, scene: Scene, options: any): T;
+
+    protected _changeTextureMode(value: number): void {
+        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+        this._textureMode = value;
+
+        // Default Setup and Reset.
+        this._texture.uScale = 1;
+        this._texture.vScale = 1;
+        this._texture.uOffset = 0;
+        this._texture.vOffset = 0;
+        this._texture.vAng = 0;
+
+        switch (value) {
+            case TextureDome.MODE_MONOSCOPIC:
+                if (this._halfDome) {
+                    this._texture.uScale = 2;
+                    this._texture.uOffset = -1;
+                }
+                break;
+            case TextureDome.MODE_SIDEBYSIDE:
+                // in half-dome mode the uScale should be double of 360 texture
+                // Use 0.99999 to boost perf by not switching program
+                this._texture.uScale = this._halfDome ? 0.99999 : 0.5;
+                const rightOffset = this._halfDome ? 0.0 : 0.5;
+                const leftOffset = this._halfDome ? -0.5 : 0.0;
+                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
+                    let isRightCamera = camera.isRightCamera;
+                    if (this._crossEye) {
+                        isRightCamera = !isRightCamera;
+                    }
+                    if (isRightCamera) {
+                        this._texture.uOffset = rightOffset;
+                    } else {
+                        this._texture.uOffset = leftOffset;
+                    }
+                });
+                break;
+            case TextureDome.MODE_TOPBOTTOM:
+                // in half-dome mode the vScale should be double of 360 texture
+                // Use 0.99999 to boost perf by not switching program
+                this._texture.vScale = this._halfDome ? 0.99999 : 0.5;
+                this._onBeforeCameraRenderObserver = this._scene.onBeforeCameraRenderObservable.add((camera) => {
+                    let isRightCamera = camera.isRightCamera;
+                    // allow "cross-eye" if left and right were switched in this mode
+                    if (this._crossEye) {
+                        isRightCamera = !isRightCamera;
+                    }
+                    this._texture.vOffset = isRightCamera ? 0.5 : 0.0;
+                });
+                break;
+        }
+    }
+
+    /**
+     * Releases resources associated with this node.
+     * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+     * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
+     */
+    public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
+        this._texture.dispose();
+        this._mesh.dispose();
+        this._material.dispose();
+
+        this._scene.onBeforeCameraRenderObservable.remove(this._onBeforeCameraRenderObserver);
+        this.onLoadErrorObservable.clear();
+
+        super.dispose(doNotRecurse, disposeMaterialAndTextures);
+    }
+}

+ 1 - 1
src/Helpers/videoDome.ts

@@ -47,7 +47,7 @@ export class VideoDome extends TextureDome<VideoTexture> {
 
     protected _initTexture(urlsOrElement: string | string[] | HTMLVideoElement, scene: Scene, options: any): VideoTexture {
         const tempOptions: VideoTextureSettings = { loop: options.loop, autoPlay: options.autoPlay, autoUpdateTexture: true, poster: options.poster };
-        const texture = new VideoTexture((this.name || "videoDome") + "_texture", urlsOrElement, scene, false, this._useDirectMapping, Texture.TRILINEAR_SAMPLINGMODE, tempOptions);
+        const texture = new VideoTexture((this.name || "videoDome") + "_texture", urlsOrElement, scene, options.generateMipMaps, this._useDirectMapping, Texture.TRILINEAR_SAMPLINGMODE, tempOptions);
         // optional configuration
         if (options.clickToPlay) {
             scene.onPointerUp = () => {

+ 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.

+ 47 - 14
src/Materials/Textures/texture.ts

@@ -1,7 +1,7 @@
 import { serialize, SerializationHelper } from "../../Misc/decorators";
 import { Observable } from "../../Misc/observable";
 import { Nullable } from "../../types";
-import { Matrix, Vector3 } from "../../Maths/math.vector";
+import { Matrix, TmpVectors, Vector3 } from "../../Maths/math.vector";
 import { BaseTexture } from "../../Materials/Textures/baseTexture";
 import { Constants } from "../../Engines/constants";
 import { _TypeStore } from '../../Misc/typeStore';
@@ -182,6 +182,12 @@ export class Texture extends BaseTexture {
     public wRotationCenter = 0.5;
 
     /**
+     * Sets this property to true to avoid deformations when rotating the texture with non-uniform scaling
+     */
+    @serialize()
+    public homogeneousRotationInUVTransform = false;
+
+    /**
      * Are mip maps generated for this texture or not.
      */
     get noMipmap(): boolean {
@@ -212,6 +218,10 @@ export class Texture extends BaseTexture {
     private _cachedVAng: number = -1;
     private _cachedWAng: number = -1;
     private _cachedProjectionMatrixId: number = -1;
+    private _cachedURotationCenter: number = -1;
+    private _cachedVRotationCenter: number = -1;
+    private _cachedWRotationCenter: number = -1;
+    private _cachedHomogeneousRotationInUVTransform: boolean = false;
     private _cachedCoordinatesMode: number = -1;
 
     /** @hidden */
@@ -456,7 +466,11 @@ export class Texture extends BaseTexture {
             this.vScale === this._cachedVScale &&
             this.uAng === this._cachedUAng &&
             this.vAng === this._cachedVAng &&
-            this.wAng === this._cachedWAng) {
+            this.wAng === this._cachedWAng &&
+            this.uRotationCenter === this._cachedURotationCenter &&
+            this.vRotationCenter === this._cachedVRotationCenter &&
+            this.wRotationCenter === this._cachedWRotationCenter &&
+            this.homogeneousRotationInUVTransform === this._cachedHomogeneousRotationInUVTransform) {
             return this._cachedTextureMatrix!;
         }
 
@@ -467,6 +481,10 @@ export class Texture extends BaseTexture {
         this._cachedUAng = this.uAng;
         this._cachedVAng = this.vAng;
         this._cachedWAng = this.wAng;
+        this._cachedURotationCenter = this.uRotationCenter;
+        this._cachedVRotationCenter = this.vRotationCenter;
+        this._cachedWRotationCenter = this.wRotationCenter;
+        this._cachedHomogeneousRotationInUVTransform = this.homogeneousRotationInUVTransform;
 
         if (!this._cachedTextureMatrix) {
             this._cachedTextureMatrix = Matrix.Zero();
@@ -478,20 +496,35 @@ export class Texture extends BaseTexture {
 
         Matrix.RotationYawPitchRollToRef(this.vAng, this.uAng, this.wAng, this._rowGenerationMatrix!);
 
-        this._prepareRowForTextureGeneration(0, 0, 0, this._t0!);
-        this._prepareRowForTextureGeneration(1.0, 0, 0, this._t1!);
-        this._prepareRowForTextureGeneration(0, 1.0, 0, this._t2!);
+        if (this.homogeneousRotationInUVTransform) {
+            Matrix.TranslationToRef(-this._cachedURotationCenter, -this._cachedVRotationCenter, -this._cachedWRotationCenter, TmpVectors.Matrix[0]);
+            Matrix.TranslationToRef(this._cachedURotationCenter, this._cachedVRotationCenter, this._cachedWRotationCenter, TmpVectors.Matrix[1]);
+            Matrix.ScalingToRef(this._cachedUScale, this._cachedVScale, 0, TmpVectors.Matrix[2]);
+            Matrix.TranslationToRef(this._cachedUOffset, this._cachedVOffset, 0, TmpVectors.Matrix[3]);
 
-        this._t1!.subtractInPlace(this._t0!);
-        this._t2!.subtractInPlace(this._t0!);
+            TmpVectors.Matrix[0].multiplyToRef(this._rowGenerationMatrix!, this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[1], this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[2], this._cachedTextureMatrix);
+            this._cachedTextureMatrix.multiplyToRef(TmpVectors.Matrix[3], this._cachedTextureMatrix);
 
-        Matrix.FromValuesToRef(
-            this._t1!.x, this._t1!.y, this._t1!.z, 0.0,
-            this._t2!.x, this._t2!.y, this._t2!.z, 0.0,
-            this._t0!.x, this._t0!.y, this._t0!.z, 0.0,
-            0.0, 0.0, 0.0, 1.0,
-            this._cachedTextureMatrix
-        );
+            // copy the translation row to the 3rd row of the matrix so that we don't need to update the shaders (which expects the translation to be on the 3rd row)
+            this._cachedTextureMatrix.setRowFromFloats(2, this._cachedTextureMatrix.m[12], this._cachedTextureMatrix.m[13], this._cachedTextureMatrix.m[14], 1);
+        } else {
+            this._prepareRowForTextureGeneration(0, 0, 0, this._t0!);
+            this._prepareRowForTextureGeneration(1.0, 0, 0, this._t1!);
+            this._prepareRowForTextureGeneration(0, 1.0, 0, this._t2!);
+
+            this._t1!.subtractInPlace(this._t0!);
+            this._t2!.subtractInPlace(this._t0!);
+
+            Matrix.FromValuesToRef(
+                this._t1!.x, this._t1!.y, this._t1!.z, 0.0,
+                this._t2!.x, this._t2!.y, this._t2!.z, 0.0,
+                this._t0!.x, this._t0!.y, this._t0!.z, 0.0,
+                0.0, 0.0, 0.0, 1.0,
+                this._cachedTextureMatrix
+            );
+        }
 
         let scene = this.getScene();
 

+ 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++;

+ 6 - 2
src/Sprites/spriteSceneComponent.ts

@@ -171,9 +171,13 @@ Scene.prototype._internalMultiPickSprites = function(ray: Ray, predicate?: (spri
 };
 
 Scene.prototype.pickSprite = function(x: number, y: number, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean, camera?: Camera): Nullable<PickingInfo> {
-    this.createPickingRayInCameraSpaceToRef(x, y, this._tempSpritePickingRay!, camera);
+    if (!this._tempSpritePickingRay) {
+        return null;
+    }
+
+    this.createPickingRayInCameraSpaceToRef(x, y, this._tempSpritePickingRay, camera);
 
-    return this._internalPickSprites(this._tempSpritePickingRay!, predicate, fastCheck, camera);
+    return this._internalPickSprites(this._tempSpritePickingRay, predicate, fastCheck, camera);
 };
 
 Scene.prototype.pickSpriteWithRay = function(ray: Ray, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean, camera?: Camera): Nullable<PickingInfo> {